Skip to content

feat: 670 per reviewer skip - #893

Open
calebmcquaid wants to merge 58 commits into
caleb/670-weighted-job-priorityfrom
caleb/670-per-reviewer-skip
Open

feat: 670 per reviewer skip#893
calebmcquaid wants to merge 58 commits into
caleb/670-weighted-job-priorityfrom
caleb/670-per-reviewer-skip

Conversation

@calebmcquaid

Copy link
Copy Markdown
Contributor

Context & Requests for Reviewers

Part 3 of 3 for #670. Previously, skipping a job just released the lock. The job came right back to the same reviewer, and skip was two separate client calls (logSkip + releaseJobLock) that could partially fail.

  • Skip is now one server-side operation: logSkip atomically logs the skip, records the job in a per-reviewer Redis skip set (sorted set scored by expiry, with a TTL backstop), and releases the lock. The client's separate ReleaseJobLock call is removed.
  • Dequeue steps past skipped jobs for that reviewer only: the scan holds skipped jobs aside under the reviewer's lock token and releases them in a finally, so they return to the shared pool immediately for everyone else.
  • getNextJob now uses block: false, so a drained queue returns null immediately instead of long-polling and hanging the reviewer's request.
  • UI: skip failures show a retry modal instead of failing silently; when no reviewable job remains (drained, or everything left is skipped by this reviewer), the reviewer is redirected to the queue list instead of a perpetual spinner.

Review focus: the held-aside-then-release pattern in dequeueNextJobWithLock, particularly lock-token semantics (the token is the reviewer's userId) and what happens if the release in finally partially fails.

Tests

  • QueueOperations.reviewerSkips.test.ts — skip set recording/expiry, dequeue filtering per reviewer, held-aside jobs released for other reviewers.

Checklist

  • If you changed anything user-facing (i.e. user interface or APIs):
    Did you update the CHANGELOG.md and related docs?

calebmcquaid and others added 5 commits July 6, 2026 21:56
* test(ncmec): add field coverage and submission tests

Add XML field-coverage regression tests for NCMEC report builders,
including XSD-order locks for the ipCaptureEvent ordering regression
class. Add a submitReport integration test using a stubbed fetchHTTP seam
against real Postgres, plus a fixture helper for NCMEC org settings.

Canonicalise webhook-sourced ipCaptureEvent objects before XML rendering
so webhook key order cannot produce out-of-order CyberTip XML.

Co-Authored-By: pi

* fix(ncmec): correct fileDetails ipCaptureEvent XSD ordering; harden tests

buildFileDetailsObject emits <ipCaptureEvent> in the wrong position relative
to <industryClassification>/<originalFileHash>. PR #869 moved it BEFORE
industryClassification, claiming NCMEC's live validator required that — but
a probe against exttest.cybertip.org proves the opposite:

  - Variant A (#869 order, ipCaptureEvent BEFORE industryClassification):
    REJECTED — cvc-complex-type.2.4.a: Invalid content was found starting
    with element 'industryClassification'. One of '{deviceId, details,
    additionalInfo}' is expected.
  - Variant B (canonical XSD order, ipCaptureEvent AFTER
    industryClassification/originalFileHash): ACCEPTED, responseCode=0.

#869 misread the original #856 error (which fires when an element lands in
the trailing deviceId/details/additionalInfo slot) and moved ipCaptureEvent
backwards to a position NCMEC actually rejects. #869's own PR description
admits the live re-test was never run before merge. This commit restores the
canonical XSD order, which the live validator accepts.

Reorders buildFileDetailsObject and the FileDetails type so ipCaptureEvent
follows industryClassification and originalFileHash, and updates the
ncmecReporting.builders.test.ts 'preserves XSD insertion order (Appendix C)'
assertion to anchor the correct order (the test had been updated in #869 to
anchor the wrong one).

Also includes test-hygiene improvements to the NCMEC #843 test work:
- fieldCoverage.test.ts: strengthen the renderXml assertion from a bare
  length>0 check to per-scenario structural XML markers (present/absent),
  so the js2xml serialization path is actually exercised. Remove the dead
  XSD.fileDetails array (buildSubmitReportObject's <report> envelope never
  contains <fileDetails>; its ordering is locked in
  ncmecReporting.builders.test.ts).
- ncmec-submission.integ.test.ts: extract a shared RecordedCall type,
  replace submitCall! non-null assertions with an early-fail type guard,
  and type the stub generic against the real FetchHTTP/CoopRequestQuery
  contract (removing the as-never param, inline cast, and final broad
  as-unknown-as-FetchHTTP). Requires exporting HandleResponseBody from
  networkingService (additive type-only export).

Co-Authored-By: pi
* fix: Support non-standard scylla ports in DB Migrator

* better compat

* whitespace
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • main

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b2fca305-8d52-4335-9f80-ed2e1ac52e7e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch caleb/670-per-reviewer-skip

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@calebmcquaid calebmcquaid changed the title Caleb/670 per reviewer skip feat: 670 per reviewer skip Jul 7, 2026
serendipty01 and others added 7 commits July 9, 2026 00:03
Co-authored-by: Pi <pi@earendil.works>
* ci: stop logging every DB query in test runs

Set DATABASE_PRINT_LOGS=false in .env.githubci (used by the docker-compose
`test` service for unit/integration tests) and comment it out in
server/.env.example (copied to server/.env by the e2e workflow) so the
default is off. Kysely still logs query errors regardless, so failures
remain visible; this only suppresses the per-query SQL/params/duration
spam that made CI logs unreadable.

Co-Authored-By: pi <pi@earendil-works>

* ci: drop redundant comment in server/.env.example

Co-Authored-By: pi <pi@earendil-works>

---------

Co-authored-by: pi <pi@earendil-works>
Co-authored-by: Cassidy James <cassidyjames@roost.tools>
* fix: close express-session store on api shutdown

connect-pg-simple keeps a recurring pruneSessions timer that calls
pool.query on its pool. makeApiServer created the store with the shared
KyselyPgPool but never closed it, so the timer kept running after
shutdown. In tests this leaked a setInterval per makeMockedServer() call
that fired pool.query on the harness's already-closed pinned connection
after each test, logging "Failed to prune sessions: Client was closed
and is not queryable" indefinitely and hanging the test worker — the
loop that forced the manual cancellation of CI run 27951870325.

Hold a reference to the store instance and call its close() in the
shutdown path. ownsPg is false (we pass in our own pool), so close() only
stops the prune timer and won't end the shared pool.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: migrate DB-backed tests to transactional harness

Move the remaining DB-backed server tests onto the transaction-rollback
harness from #732 so they get per-test isolation with no hand-written
cleanup.

- Convert the makeMockedServer/getBottle-based tests to
  makeTransactionalTestWithFixture, dropping manual org/user/queue/action
  deletes and KyselyPg.destroy() teardown: userKyselyPersistenceFindByEmailAndOrg,
  resolveSamlUser, and the MRT module tests (CommentOperations, JobRouting,
  QueueOperations, ReporterInvalidation).
- Rewrite moderationConfigService and manualReviewToolService so every test
  is self-contained: each creates its own fresh org (rolled back
  automatically) rather than sharing a suite-scoped org. This removes the
  cross-test ordering/accumulator dependency in moderationConfigService and
  the hardcoded staging-seed-data dependency in manualReviewToolService.

Scylla-touching tests (itemInvestigationService) keep uid-based isolation,
since the Postgres-only harness can't roll those back.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: migrate UserReportSweep and userStrikeService to transactional harness

Two DB-backed test suites were missed by the initial migration commit:

- manualReviewToolService/modules/UserReportSweep.test.ts: a sibling of
  the four MRT module tests already migrated (CommentOperations, JobRouting,
  QueueOperations, ReporterInvalidation). It used makeTestWithFixture +
  getBottle with hand-written cleanup (createOrg/createUser/createMrtQueue
  teardown + KyselyPg/KyselyPgReadReplica.destroy()). Converted to
  makeTransactionalTestWithFixture; cleanup is now automatic rollback.
- userStrikeService/userStrikeService.test.ts: used beforeAll/afterAll with
  a shared getBottle container and uid-based isolation. Converted to
  makeTransactionalTestWithFixture for true per-test rollback isolation.
  Also fixes a copy-paste describe block name ('Item Investigation Service'
  -> 'User Strike Service').

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: resolve typecheck errors in migrated test files

Two files migrated by the prior commit had TypeScript errors that broke
`tsc` (and therefore `check_api_server` and the e2e server start):

- manualReviewToolService.test.ts: the 'records an AUTOMATIC_CLOSE decision
  with no human reviewer' test was left as a bare `it(...)` referencing an
  out-of-scope `mrtService` and hardcoded staging `orgId`/'queueId'
  ('e7c89ce7729'/'1') — the very seed-data dependency the migration was
  meant to remove. Convert it to `testWithQueue()` like its siblings, using
  the fixture's `mrtService`/`org.id`/`queue.id`.
- moderationConfigService.test.ts: the 'should return actions for a rule
  scoped to the caller org' snapshot read `it.id` from
  `getActionsForRuleId`, which returns `{ action, parameters }[]` —
  should be `it.action.id` (as on main). Also add the now-present
  `email` field role to the #createUserType inline snapshot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: create real user in migrated JobRouting fixture

The transactional JobRouting fixture accidentally replaced the pre-migration
createUser() call with a bare uid(). createManualReviewQueue validates queue
users, so setup failed after creating org/item-type rows but before the fixture
returned. Because makeTestWithFixture cannot run cleanup when setup throws,
the outer transaction stayed idle-in-transaction and later tests blocked on the
item_type_versions materialized-view refresh, timing out check_api_server.

Keep the transactional harness, but create a real user and pass user.id to the
queue setup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: restore QueueOperations org-scoping tests

The rebase conflict resolution for QueueOperations.test.ts accidentally kept
the pre-#872 branch side and dropped the two-org org-scoping regression tests
that now exist on main. Restore testWithTwoOrgs and its cross-org access tests,
using the transactional harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test: configure decision-reason settings through service API

Replace the decision-reason test helpers' direct writes to
manual_review_tool_settings with a behavior-shaped helper that uses the
ManualReviewToolService update API. The tests still exercise persisted org
settings through submitDecision, but no longer couple setup to table and column
names.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* style: remove api shutdown explanatory comments

Remove the comments added around the express-session store shutdown while keeping the shutdown behavior unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* test: add MRT video playback e2e coverage

Co-Authored-By: Pi <pi@users.noreply.github.com>

* test: use Playwright base URL for media fixtures

Co-Authored-By: Pi <pi@users.noreply.github.com>

---------

Co-authored-by: Pi <pi@users.noreply.github.com>
);
selectedRelatedActionsSetter(actions);
};
const setSelectedRelatedActions = useCallback(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

are you adding useCallback in response to an observed performance issue, or is it hypothetical?

);
selectedRelatedActionsSetter(actions);
},
[actionStore],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we not have selectedRelatedActionsSetter in the dependency array, too?

setSelectedRelatedActions([]);
setDecisionReason(undefined);
};
}, [setSelectedRelatedActions]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

why does this dependency array only have one of the functions?

Comment thread CHANGELOG.md Outdated

**Full Changelog**: https://github.com/roostorg/coop/compare/1.0.2...main

## Review Console

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(as in another PR) we should probably decide how we want to handle the changelog, i.e. writing to it per-PR or manually writing it all once in a while

cc @cassidyjames

Comment thread server/services/manualReviewToolService/manualReviewToolService.ts Outdated
Comment thread server/services/manualReviewToolService/modules/QueueOperations.ts Outdated
return null;
}

static readonly REVIEWER_SKIP_TTL_MS = 30 * 60 * 1000;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

hmm, how come we only skip for 30 minutes?

i guess in general -- what's the user story we imagine when someone skips a job? is it "i am not qualified to review this", "this is a hard job and i don't wanna deal with it / lower my average handling time by taking it", or something else? cc @julietshen

julietshen and others added 10 commits July 14, 2026 22:09
…tore it as null in decision log (#913)

* fix(mrt): store null for unparseable item createdAt in decision log

Submitting a decision failed with a 500 ("Job submission failed. Please
try again.") for any job whose item carries a truthy but unparseable
createdAt value. `#logDecision` passed `new Date(itemCreatedAtField)`
straight into the `item_created_at` timestamptz column; an unparseable
value yields an Invalid Date, which the pg driver serializes to a NaN
string that Postgres rejects (22007), failing the whole decision insert.
The decision is never recorded and the job is never removed, so the task
is stuck in the queue.

Normal item submissions can't reach this state because the DATETIME field
handler validates dates at intake. A bad value only arrives via a path
that skips that validation (e.g. system-generated reports, or a createdAt
role mapped to a non-DATETIME field).

Normalize at the write boundary: parse the value and store null when it
is not a valid date. The column is already nullable, so the decision
records and the task clears. Adds a unit regression test for the
normalization helper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEU6YHFiBYfTqjgM5Vyt9n

* docs(changelog): note the unparseable createdAt decision fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEU6YHFiBYfTqjgM5Vyt9n

* fix(mrt): preserve epoch 0 in parseItemCreatedAt

Address review: `!value` treated a numeric 0 (a valid 1970-01-01 epoch) as
empty. Guard only null/undefined/empty-string instead, and cover epoch 0 in
the test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEU6YHFiBYfTqjgM5Vyt9n

* fix(mrt): record unparseable item createdAt values

Address review: surface invalid createdAt values instead of silently
nulling them. When a present createdAt can't be parsed, emit a tracer
span (job id, org id, the raw value) so the bad data is diagnosable and
can be backfilled. The decision still saves with a null item_created_at.

new Date() returns an Invalid Date rather than throwing, so this detects
the invalid parse rather than wrapping in try/catch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEU6YHFiBYfTqjgM5Vyt9n

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…nal (#918)

* Make Scylla-backed features (item investigation, user strikes) optional

* Address review: extract shared flag helper, use @ts-expect-error

Resolve CodeRabbit nitpicks on PR #918:
- Extract the ITEM_INVESTIGATION_AND_STRIKES_ENABLED parsing into a single
  exported helper (itemInvestigationAndStrikesEnabled) in noOpScylla.ts; the
  iocContainer factory and the unit test now share it instead of keeping two
  copies in sync (DRY).
- Keep the helper a pure function of its argument (env is read at the call
  site) so the default-enabled test is independent of the ambient environment.
- Replace the 'as unknown as' cast in NoOpScylla.insert() with a scoped
  @ts-expect-error and justifying comment, per repo guidelines.

Co-Authored-By: Rovo Dev <rovodev@atlassian.com>

* Fixed formatting

* ci: re-trigger E2E (investigate investigation.spec flake vs regression)

* Gate user strike logic behind ITEM_INVESTIGATION_AND_STRIKES_ENABLED flag

When the flag is false, applyUserStrikeFromPublishedActions early-returns
to avoid running strike threshold checks against always-zero counts from
the NoOpScylla, which would cause escalation actions to fire unexpectedly.

---------

Co-authored-by: Rovo Dev <rovodev@atlassian.com>
Co-authored-by: Juan Mrad <juansmrad@gmail.com>
Add paths filter so the GitHub Actions security scan only triggers
when .github/workflows/ files are modified, instead of on every PR.
#909)

getOldestJobCreatedAtForExistingQueue fetched the first waiting/delayed
job with queue.getJobs([state], 0, 0), but BullMQ's getJobs defaults to
descending order, so index 0 is the most recently added job. The MRT
queues dashboard's Oldest Task Age column therefore showed the newest
task's age. Switch to BullMQ's getWaiting/getDelayed getters, which
return jobs oldest-first.

Adds a regression test that enqueues an older job then a newer one and
asserts the older createdAt is returned; it fails against the previous
implementation. The dummy-job payload builder it needs was previously
copied per test file, so it is extracted into a shared
test/fixtureHelpers/makeDummyMrtJobPayload.ts fixture (using
instantiateOpaqueType instead of eslint-disabled type assertions).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Juan Mrad <juansmrad@gmail.com>
…Created At (#916)

* fix(mrt): guard client date rendering against unparseable createdAt

Unguarded date-fns format/formatDistanceToNow calls threw
RangeError: Invalid time value on an unparseable createdAt, unmounting
the task detail and queue preview into the "Something Went Wrong" error
boundary. Add safeFormat/safeFormatDistanceToNow helpers that fall back
to "Unknown" for invalid dates, and use them on the MRT render path.

Client-side counterpart to the server-side decision-log fix in #913.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AKvvCLkcC7LM1znjzvDjDA

* add guard on more functions to prevent errors on invalid timezone formats.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Juan Mrad <juansmrad@gmail.com>
The testOrgs allowlist (['4def6a77d6a','acc701627cb']) hard-coded two
org IDs that were silently suppressed with PERMANENT_ERROR before any
network call. Hard-coded org IDs in source don't belong here; the
NCMEC_ENV-based sandbox/production routing already handles test vs.
prod submissions.

Co-Authored-By: pi
Bumps the db-prod-security group with 1 update in the /db directory: [fast-uri](https://github.com/fastify/fast-uri).


Updates `fast-uri` from 3.1.2 to 3.1.4
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](fastify/fast-uri@v3.1.2...v3.1.4)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.4
  dependency-type: indirect
  dependency-group: db-prod-security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the migrator-prod-security group with 1 update in the /migrator directory: [fast-uri](https://github.com/fastify/fast-uri).


Updates `fast-uri` from 3.1.2 to 3.1.4
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](fastify/fast-uri@v3.1.2...v3.1.4)

---
updated-dependencies:
- dependency-name: fast-uri
  dependency-version: 3.1.4
  dependency-type: indirect
  dependency-group: migrator-prod-security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [undici](https://github.com/nodejs/undici) from 7.27.1 to 7.29.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](nodejs/undici@v7.27.1...v7.29.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 7.29.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [immutable](https://github.com/immutable-js/immutable-js) from 5.1.5 to 5.1.9.
- [Release notes](https://github.com/immutable-js/immutable-js/releases)
- [Changelog](https://github.com/immutable-js/immutable-js/blob/main/CHANGELOG.md)
- [Commits](immutable-js/immutable-js@v5.1.5...v5.1.9)

---
updated-dependencies:
- dependency-name: immutable
  dependency-version: 5.1.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
dependabot Bot and others added 21 commits July 27, 2026 17:38
* build(deps-dev): bump js-yaml from 4.3.0 to 5.2.2 in /server

Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.0 to 5.2.2.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](nodeca/js-yaml@4.3.0...5.2.2)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 5.2.2
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: use js-yaml named export

Co-Authored-By: Codex <noreply@openai.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tao Bojlén <git@btao.org>
Co-authored-by: Codex <noreply@openai.com>
…ith 2 updates (#943)

Bumps the server-prod-security group with 2 updates in the /server directory: [body-parser](https://github.com/expressjs/body-parser) and [fast-uri](https://github.com/fastify/fast-uri).


Updates `body-parser` from 2.2.2 to 2.3.0
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](expressjs/body-parser@v2.2.2...v2.3.0)

Updates `fast-uri` from 3.1.2 to 3.1.4
- [Release notes](https://github.com/fastify/fast-uri/releases)
- [Commits](fastify/fast-uri@v3.1.2...v3.1.4)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-version: 2.3.0
  dependency-type: indirect
  dependency-group: server-prod-security
- dependency-name: fast-uri
  dependency-version: 3.1.4
  dependency-type: indirect
  dependency-group: server-prod-security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…ctory with 2 updates (#941)

Bumps the nodejs-instrumentation-prod group with 2 updates in the /nodejs-instrumentation directory: [@opentelemetry/semantic-conventions](https://github.com/open-telemetry/opentelemetry-js) and [@opentelemetry/winston-transport](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/HEAD/packages/winston-transport).


Updates `@opentelemetry/semantic-conventions` from 1.41.1 to 1.43.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-js/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-js/blob/main/CHANGELOG.md)
- [Commits](open-telemetry/opentelemetry-js@semconv/v1.41.1...semconv/v1.43.0)

Updates `@opentelemetry/winston-transport` from 0.29.0 to 0.30.0
- [Release notes](https://github.com/open-telemetry/opentelemetry-js-contrib/releases)
- [Changelog](https://github.com/open-telemetry/opentelemetry-js-contrib/blob/main/packages/winston-transport/CHANGELOG.md)
- [Commits](https://github.com/open-telemetry/opentelemetry-js-contrib/commits/host-metrics-v0.30.0/packages/winston-transport)

---
updated-dependencies:
- dependency-name: "@opentelemetry/semantic-conventions"
  dependency-version: 1.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nodejs-instrumentation-prod
- dependency-name: "@opentelemetry/winston-transport"
  dependency-version: 0.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: nodejs-instrumentation-prod
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.12 to 8.5.23.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.12...8.5.23)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.23
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* Replace dotenv with Node's built-in --env-file-if-exists

Node 24 (this repo's runtime) has a built-in  flag
that loads a .env file when present and silently no-ops when absent -
exactly the behavior dotenv/config provided. Remove the dotenv package
from server and db, and switch all  invocations
to .

Key details:
-  (not ) preserves the silent no-op
  when .env is absent, matching dotenv/config. CI/prod get env from the
  docker-compose  directive, so .env is not present there.
-  is NOT allowed in NODE_OPTIONS, so the test
  scripts were restructured to invoke  directly while keeping the ts-node loader in
  NODE_OPTIONS.
- Removed  from 4 test/e2e fixture files; env
  is now loaded by the node flag before any module reads process.env.
- Removed dotenv from db/knip.json ignoreDependencies (no longer needed).
- Updated db/src/index.ts shebang and db/README.md.

uuid was NOT removed: the server uses v1 (timestamp) UUIDs in 23 files,
and crypto.randomUUID() only generates v4 - there is no Node built-in
replacement for v1.

Co-Authored-By: pi

* Remove redundant env-loading comments

The comments restated the env-loading mechanism; removing them keeps the
files clean. Behavior unchanged.

Co-Authored-By: pi

* Load env in test:e2e via --env-file-if-exists

The e2e suite boots the IoC container (via the coop.ts fixture), which
reads env vars like UI_URL at module load. Removing the
`import 'dotenv/config'` from the fixture left nothing to load
server/.env when playwright runs, so CI failed with
"Missing env var UI_URL".

Invoke playwright through `node --env-file-if-exists=.env` (the same
pattern used for jest) so env loads before any test module imports the
container. CI gets server/.env from the workflow's `cp server/.env.example
server/.env` step; the -if-exists variant no-ops if it's absent.

Co-Authored-By: pi
… updates (#949)

* build(deps-dev): bump the client-dev group across 1 directory with 19 updates

Bumps the client-dev group with 18 updates in the /client directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/compat](https://github.com/eslint/rewrite/tree/HEAD/packages/compat) | `2.0.3` | `2.1.0` |
| [@eslint/eslintrc](https://github.com/eslint/eslintrc) | `3.3.5` | `3.3.6` |
| [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) | `5.16.5` | `5.17.0` |
| [@types/google.maps](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/google.maps) | `3.51.0` | `3.65.2` |
| [@types/latlon-geohash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/latlon-geohash) | `2.0.0` | `2.0.4` |
| [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) | `4.14.191` | `4.17.24` |
| [@types/react-beautiful-dnd](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-beautiful-dnd) | `13.1.4` | `13.1.8` |
| [@types/react-csv](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-csv) | `1.1.3` | `1.1.10` |
| [@types/react-table](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-table) | `7.7.14` | `7.7.20` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.57.2` | `8.64.0` |
| [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) | `5.1.4` | `5.2.0` |
| [autoprefixer](https://github.com/postcss/autoprefixer) | `10.4.13` | `10.5.4` |
| [eslint](https://github.com/eslint/eslint) | `9.39.4` | `9.39.5` |
| [eslint-plugin-react-hooks](https://github.com/facebook/react/tree/HEAD/packages/eslint-plugin-react-hooks) | `7.0.1` | `7.1.1` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `6.16.1` | `6.27.0` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.3.2` | `5.9.3` |
| [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `7.3.5` | `7.3.6` |
| [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) | `4.1.0` | `4.1.10` |



Updates `@eslint/compat` from 2.0.3 to 2.1.0
- [Release notes](https://github.com/eslint/rewrite/releases)
- [Changelog](https://github.com/eslint/rewrite/blob/main/packages/compat/CHANGELOG.md)
- [Commits](https://github.com/eslint/rewrite/commits/compat-v2.1.0/packages/compat)

Updates `@eslint/eslintrc` from 3.3.5 to 3.3.6
- [Release notes](https://github.com/eslint/eslintrc/releases)
- [Changelog](https://github.com/eslint/eslintrc/blob/main/CHANGELOG.md)
- [Commits](eslint/eslintrc@eslintrc-v3.3.5...eslintrc-v3.3.6)

Updates `@testing-library/jest-dom` from 5.16.5 to 5.17.0
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](testing-library/jest-dom@v5.16.5...v5.17.0)

Updates `@types/google.maps` from 3.51.0 to 3.65.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/google.maps)

Updates `@types/latlon-geohash` from 2.0.0 to 2.0.4
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/latlon-geohash)

Updates `@types/lodash` from 4.14.191 to 4.17.24
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

Updates `@types/react-beautiful-dnd` from 13.1.4 to 13.1.8
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-beautiful-dnd)

Updates `@types/react-csv` from 1.1.3 to 1.1.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-csv)

Updates `@types/react-table` from 7.7.14 to 7.7.20
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-table)

Updates `@typescript-eslint/eslint-plugin` from 8.57.2 to 8.64.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.64.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.57.2 to 8.65.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/parser)

Updates `@vitejs/plugin-react` from 5.1.4 to 5.2.0
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/plugin-react@5.2.0/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@5.2.0/packages/plugin-react)

Updates `autoprefixer` from 10.4.13 to 10.5.4
- [Release notes](https://github.com/postcss/autoprefixer/releases)
- [Changelog](https://github.com/postcss/autoprefixer/blob/main/CHANGELOG.md)
- [Commits](postcss/autoprefixer@10.4.13...10.5.4)

Updates `eslint` from 9.39.4 to 9.39.5
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](eslint/eslint@v9.39.4...v9.39.5)

Updates `eslint-plugin-react-hooks` from 7.0.1 to 7.1.1
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/react/react/blob/main/packages/eslint-plugin-react-hooks/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/eslint-plugin-react-hooks@7.1.1/packages/eslint-plugin-react-hooks)

Updates `knip` from 6.16.1 to 6.27.0
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.27.0/packages/knip)

Updates `typescript` from 5.3.2 to 5.9.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](microsoft/TypeScript@v5.3.2...v5.9.3)

Updates `vite` from 7.3.5 to 7.3.6
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.3.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.3.6/packages/vite)

Updates `vitest` from 4.1.0 to 4.1.10
- [Release notes](https://github.com/vitest-dev/vitest/releases)
- [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md)
- [Commits](https://github.com/vitest-dev/vitest/commits/v4.1.10/packages/vitest)

---
updated-dependencies:
- dependency-name: "@eslint/compat"
  dependency-version: 2.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: "@eslint/eslintrc"
  dependency-version: 3.3.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: client-dev
- dependency-name: "@testing-library/jest-dom"
  dependency-version: 5.17.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: "@types/google.maps"
  dependency-version: 3.65.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: "@types/latlon-geohash"
  dependency-version: 2.0.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: client-dev
- dependency-name: "@types/lodash"
  dependency-version: 4.17.24
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: "@types/react-beautiful-dnd"
  dependency-version: 13.1.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: client-dev
- dependency-name: "@types/react-csv"
  dependency-version: 1.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: client-dev
- dependency-name: "@types/react-table"
  dependency-version: 7.7.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: client-dev
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.64.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.65.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 5.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: autoprefixer
  dependency-version: 10.5.4
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: eslint
  dependency-version: 9.39.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: client-dev
- dependency-name: eslint-plugin-react-hooks
  dependency-version: 7.1.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: knip
  dependency-version: 6.27.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: typescript
  dependency-version: 5.9.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: client-dev
- dependency-name: vite
  dependency-version: 7.3.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: client-dev
- dependency-name: vitest
  dependency-version: 4.1.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: client-dev
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix client checks after dependency updates

Co-Authored-By: Codex <noreply@openai.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tao Bojlén <git@btao.org>
Co-authored-by: Codex <noreply@openai.com>
… updates (#948)

* build(deps-dev): bump the server-dev group across 1 directory with 25 updates

Bumps the server-dev group with 24 updates in the /server directory:

| Package | From | To |
| --- | --- | --- |
| [@eslint/compat](https://github.com/eslint/rewrite/tree/HEAD/packages/compat) | `2.0.3` | `2.1.0` |
| [@eslint/eslintrc](https://github.com/eslint/eslintrc) | `3.3.5` | `3.3.6` |
| [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.4` | `9.39.5` |
| [@playwright/test](https://github.com/microsoft/playwright) | `1.61.0` | `1.61.1` |
| [@types/cookie-parser](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/cookie-parser) | `1.4.3` | `1.4.10` |
| [@types/debug](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/debug) | `4.1.12` | `4.1.13` |
| [@types/express-serve-static-core](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/express-serve-static-core) | `5.1.1` | `5.1.2` |
| [@types/js-yaml](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/js-yaml) | `4.0.5` | `4.0.9` |
| [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) | `4.14.198` | `4.17.24` |
| [@types/morgan](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/morgan) | `1.9.4` | `1.9.10` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `18.17.3` | `18.19.130` |
| [@types/validator](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/validator) | `13.9.0` | `13.15.10` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.59.0` | `8.65.0` |
| [eslint](https://github.com/eslint/eslint) | `9.39.4` | `9.39.5` |
| [eslint-import-resolver-typescript](https://github.com/import-js/eslint-import-resolver-typescript) | `3.6.0` | `3.10.1` |
| [eslint-plugin-functional](https://github.com/eslint-functional/eslint-plugin-functional) | `9.0.4` | `9.0.5` |
| [eslint-plugin-jsdoc](https://github.com/gajus/eslint-plugin-jsdoc) | `62.5.4` | `62.9.0` |
| [eslint-plugin-promise](https://github.com/eslint-community/eslint-plugin-promise) | `7.2.1` | `7.3.0` |
| [eslint-plugin-switch-statement](https://github.com/ethanresnick/eslint-plugin-exhaustive-switch) | `0.0.11` | `0.0.12` |
| [jest-light-runner](https://github.com/nicolo-ribaudo/jest-light-runner) | `0.4.1` | `0.8.1` |
| [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) | `6.16.1` | `6.27.0` |
| [ts-node](https://github.com/TypeStrong/ts-node) | `10.9.1` | `10.9.2` |
| [typescript](https://github.com/microsoft/TypeScript) | `5.5.2` | `5.9.3` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.57.2` | `8.65.0` |



Updates `@eslint/compat` from 2.0.3 to 2.1.0
- [Release notes](https://github.com/eslint/rewrite/releases)
- [Changelog](https://github.com/eslint/rewrite/blob/main/packages/compat/CHANGELOG.md)
- [Commits](https://github.com/eslint/rewrite/commits/compat-v2.1.0/packages/compat)

Updates `@eslint/eslintrc` from 3.3.5 to 3.3.6
- [Release notes](https://github.com/eslint/eslintrc/releases)
- [Changelog](https://github.com/eslint/eslintrc/blob/main/CHANGELOG.md)
- [Commits](eslint/eslintrc@eslintrc-v3.3.5...eslintrc-v3.3.6)

Updates `@eslint/js` from 9.39.4 to 9.39.5
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/commits/v9.39.5/packages/js)

Updates `@playwright/test` from 1.61.0 to 1.61.1
- [Release notes](https://github.com/microsoft/playwright/releases)
- [Commits](microsoft/playwright@v1.61.0...v1.61.1)

Updates `@types/cookie-parser` from 1.4.3 to 1.4.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/cookie-parser)

Updates `@types/debug` from 4.1.12 to 4.1.13
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/debug)

Updates `@types/express-serve-static-core` from 5.1.1 to 5.1.2
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/express-serve-static-core)

Updates `@types/js-yaml` from 4.0.5 to 4.0.9
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/js-yaml)

Updates `@types/lodash` from 4.14.198 to 4.17.24
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash)

Updates `@types/morgan` from 1.9.4 to 1.9.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/morgan)

Updates `@types/node` from 18.17.3 to 18.19.130
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `@types/validator` from 13.9.0 to 13.15.10
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/validator)

Updates `@typescript-eslint/eslint-plugin` from 8.59.0 to 8.65.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.59.0 to 8.65.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/parser)

Updates `eslint` from 9.39.4 to 9.39.5
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](eslint/eslint@v9.39.4...v9.39.5)

Updates `eslint-import-resolver-typescript` from 3.6.0 to 3.10.1
- [Release notes](https://github.com/import-js/eslint-import-resolver-typescript/releases)
- [Changelog](https://github.com/import-js/eslint-import-resolver-typescript/blob/v3.10.1/CHANGELOG.md)
- [Commits](import-js/eslint-import-resolver-typescript@v3.6.0...v3.10.1)

Updates `eslint-plugin-functional` from 9.0.4 to 9.0.5
- [Release notes](https://github.com/eslint-functional/eslint-plugin-functional/releases)
- [Changelog](https://github.com/eslint-functional/eslint-plugin-functional/blob/main/CHANGELOG.md)
- [Commits](eslint-functional/eslint-plugin-functional@v9.0.4...v9.0.5)

Updates `eslint-plugin-jsdoc` from 62.5.4 to 62.9.0
- [Release notes](https://github.com/gajus/eslint-plugin-jsdoc/releases)
- [Commits](gajus/eslint-plugin-jsdoc@v62.5.4...v62.9.0)

Updates `eslint-plugin-promise` from 7.2.1 to 7.3.0
- [Release notes](https://github.com/eslint-community/eslint-plugin-promise/releases)
- [Changelog](https://github.com/eslint-community/eslint-plugin-promise/blob/main/CHANGELOG.md)
- [Commits](eslint-community/eslint-plugin-promise@v7.2.1...v7.3.0)

Updates `eslint-plugin-switch-statement` from 0.0.11 to 0.0.12
- [Commits](https://github.com/ethanresnick/eslint-plugin-exhaustive-switch/commits)

Updates `jest-light-runner` from 0.4.1 to 0.8.1
- [Release notes](https://github.com/nicolo-ribaudo/jest-light-runner/releases)
- [Commits](nicolo-ribaudo/jest-light-runner@v0.4.1...v0.8.1)

Updates `knip` from 6.16.1 to 6.27.0
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.27.0/packages/knip)

Updates `ts-node` from 10.9.1 to 10.9.2
- [Release notes](https://github.com/TypeStrong/ts-node/releases)
- [Changelog](https://github.com/TypeStrong/ts-node/blob/main/development-docs/release-template.md)
- [Commits](TypeStrong/ts-node@v10.9.1...v10.9.2)

Updates `typescript` from 5.5.2 to 5.9.3
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](microsoft/TypeScript@v5.5.2...v5.9.3)

Updates `typescript-eslint` from 8.57.2 to 8.65.0
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: "@eslint/compat"
  dependency-version: 2.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: "@eslint/eslintrc"
  dependency-version: 3.3.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: "@eslint/js"
  dependency-version: 9.39.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: "@playwright/test"
  dependency-version: 1.61.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: "@types/cookie-parser"
  dependency-version: 1.4.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: "@types/debug"
  dependency-version: 4.1.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: "@types/express-serve-static-core"
  dependency-version: 5.1.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: "@types/js-yaml"
  dependency-version: 4.0.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: "@types/lodash"
  dependency-version: 4.17.24
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: "@types/morgan"
  dependency-version: 1.9.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: "@types/node"
  dependency-version: 18.19.130
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: "@types/validator"
  dependency-version: 13.15.10
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.64.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.65.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: eslint
  dependency-version: 9.39.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: eslint-import-resolver-typescript
  dependency-version: 3.10.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: eslint-plugin-functional
  dependency-version: 9.0.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: eslint-plugin-jsdoc
  dependency-version: 62.9.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: eslint-plugin-promise
  dependency-version: 7.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: eslint-plugin-switch-statement
  dependency-version: 0.0.12
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: jest-light-runner
  dependency-version: 0.8.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: knip
  dependency-version: 6.27.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: ts-node
  dependency-version: 10.9.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: server-dev
- dependency-name: typescript
  dependency-version: 5.9.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
- dependency-name: typescript-eslint
  dependency-version: 8.64.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: server-dev
...

Signed-off-by: dependabot[bot] <support@github.com>

* Fix server checks after dependency updates

Co-Authored-By: Codex <noreply@openai.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tao Bojlén <git@btao.org>
Co-authored-by: Codex <noreply@openai.com>
…dates (#942)

* build(deps-dev): bump the root-dev group across 1 directory with 3 updates

Bumps the root-dev group with 3 updates in the / directory: [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node), [prettier](https://github.com/prettier/prettier) and [@parcel/watcher](https://github.com/parcel-bundler/watcher).


Updates `@types/node` from 24.12.2 to 24.13.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

Updates `prettier` from 3.8.3 to 3.9.6
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](prettier/prettier@3.8.3...3.9.6)

Updates `@parcel/watcher` from 2.5.6 to 2.6.0
- [Release notes](https://github.com/parcel-bundler/watcher/releases)
- [Commits](parcel-bundler/watcher@v2.5.6...v2.6.0)

---
updated-dependencies:
- dependency-name: "@parcel/watcher"
  dependency-version: 2.6.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: root-dev
- dependency-name: "@types/node"
  dependency-version: 24.13.3
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: root-dev
- dependency-name: prettier
  dependency-version: 3.9.5
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: root-dev
...

Signed-off-by: dependabot[bot] <support@github.com>

* Format code with Prettier 3.9

Co-Authored-By: Codex <noreply@openai.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tao Bojlén <git@btao.org>
Co-authored-by: Codex <noreply@openai.com>
* feat(mrt): add memoized CollapsibleText component (#870)

Co-Authored-By: pi

* feat(mrt): collapse long STRING fields with Read more (#870)

Co-Authored-By: pi

* fix(mrt): drop horizontal scroll for text containers (#870)

Co-Authored-By: pi

* fix(mrt): remove page-wide horizontal scroll from review view (#870)

Co-Authored-By: pi

* test(mrt): strengthen CollapsibleText grapheme test, fix stale comment (#870)

Co-Authored-By: pi

* fix(mrt): allow string field flex item to shrink for text wrapping (#870)

The FieldComponent wrapper sat inside FieldsComponent's flex flex-wrap
container with the default min-width: auto, so a flex item containing a
long unbroken string expanded to the string's intrinsic width instead
of wrapping. break-words and WebkitLineClamp only take effect when the
element has a bounded content width, so the CollapsibleText Read more
toggle appeared but did nothing on huge unbroken tokens. Adding min-w-0
lets the flex item shrink below its content width so wrapping and the
line clamp take effect.

Co-Authored-By: pi

* fix(mrt): short-circuit grapheme count, reset expanded on text change (#870)

Address review feedback on CollapsibleText:

- countGraphemes → exceedsGraphemeThreshold: stop iterating once the
  count is known to exceed maxGraphemes, so a 1MB string segments at
  most maxGraphemes+1 graphemes instead of all of them.
- Reset the expanded state when the text prop changes. When navigating
  between review jobs that reuse the same field name, React reuses the
  CollapsibleText instance; without the reset, a newly loaded long
  value inherited the previous job's expanded state.
- The custom-thresholds test now asserts the maxLines prop lands on the
  clamped div's WebkitLineClamp style, independently verifying the
  line-count constraint (previously maxLines was passed but untested).
- New test: expanding one long value then rerendering with a different
  long value restores the collapsed state.

Co-Authored-By: pi

* test(mrt): strengthen collapsible text thresholds (#870)

Co-Authored-By: pi
Reconcile the client and server lockfiles against current main, then apply the non-breaking fixes available through npm audit fix.

Client updates include the Babel 7.29.7 security release and patched brace-expansion versions. Server updates include js-yaml 3.15.0, protobufjs 7.6.5, patched brace-expansion versions, and compatible Cassandra dependency resolution.

Remaining audit findings require breaking upgrades across React Router, ESLint/Storybook, Cassandra, or Jest. Keep those separately scoped; the existing dev-only piscina risk remains accepted here.

Co-Authored-By: pi-coding-agent

Co-Authored-By: Codex
* Eliminate manual review history item refetches

Co-Authored-By: Codex <codex@openai.com>

* Remove manual review history regression test

Co-Authored-By: Codex <codex@openai.com>

---------

Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Codex <codex@openai.com>
* test: add regression test for RetryFailedNcmecDecisionsJob NCMEC_ENV

* fix: RetryFailedNcmecDecisionsJob honors NCMEC_ENV

The retry worker hardcoded isTest=false, so retries in dev/staging
(NCMEC_ENV unset or non-production) submitted to the real NCMEC
endpoint (report.cybertip.org) instead of the test endpoint
(exttest.cybertip.org). It also published post-submit actions
unconditionally, unlike the sibling call sites which gate on !isTest.

Compute isTest from NCMEC_ENV the same way as the other two submit
sites (iocContainer onRecordDecision path and retryNcmecSubmission),
pass it to submitReport, and gate publishActions on !isTest.

Co-Authored-By: pi

* refactor: drop redundant NCMEC_ENV comment in RetryFailedNcmecDecisionsJob

Co-Authored-By: pi
* Clarify approval requirements for routine commands

Co-Authored-By: OpenAI Codex <codex@openai.com>

* Refine routine command approval guidance

Co-Authored-By: OpenAI Codex <codex@openai.com>

---------

Co-authored-by: OpenAI Codex <codex@openai.com>
* [Fix] Refuse queue deletion when routing rules still reference it

Fixes issue #738 (design #1).

Changes the FK constraints on routing_rules.destination_queue_id and
appeals_routing_rules.destination_queue_id from ON DELETE CASCADE to
ON DELETE RESTRICT. The service now catches the resulting FK violation
and throws QueueHasDependentRoutingRulesError, naming the blocking
rules, instead of silently cascade-deleting them and breaking routing.

The client's previously silent onError handler is replaced with a modal
that surfaces the server's error message so the user knows which rules
to update before retrying.

Regression tests cover both routing_rules and appeals_routing_rules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* [Queue Delete behavior] Put blocking rules on their own line, indent, and link to rules page

* Remove accidentally included test code

* [Fix] Update test helper to delete routing rules before queue deletion

deleteManualReviewQueueForTestsDO_NOT_USE was written when routing_rules
had CASCADE FKs on destination_queue_id. Now that the FK is RESTRICT,
the helper must explicitly delete referencing routing rules and appeals
routing rules before removing the queue, or the DB rejects the delete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* [Fix] Address PR reviewer feedback on queue delete behavior

- Move queue.obliterate() after DB transaction so Redis jobs aren't
  destroyed if deletion is blocked by FK constraints
- Gate FK error mapping on specific constraint names to avoid swallowing
  unrelated 23503 errors from future RESTRICT FKs
- Add org_id filter to routing rule deletes in test helper
- Validate JSON.parse result is string[] before assigning to ruleNames
- Use index as React key to avoid collisions on duplicate rule names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* [Fix] Resolve CI failures after rebase on main

Rename the routing-rules migration to a fresh timestamp since main
gained a later migration (add_sepia) during the rebase, and dedupe
the two new QueueOperations.test.ts cases behind a shared helper to
get the file back under the 500-line lint limit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* [Fix] Address CodeRabbit feedback: log obliterate() failures after commit

If queue.obliterate() throws after the DB delete has already committed,
a retry would see numDeletedRows === 0n, skip obliterate() entirely, and
silently leave orphaned Bull/Redis data behind. Wrap it in try/catch and
surface the failure to the active tracing span, matching the best-effort
cleanup pattern used elsewhere (e.g. UserApi.logout).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* address code review

* make lint happy

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Juan Mrad <juansmrad@gmail.com>
* Add self-harm/intent and self-harm/instructions signals

Round out the omni-moderation self-harm coverage that the prior commit
left lopsided. OpenAI scores both subcategories for text *and* image
inputs; we had neither.

New signal classes (4):
- OpenAiSelfHarmIntentTextSignal / OpenAiSelfHarmIntentImageSignal
- OpenAiSelfHarmInstructionsTextSignal / OpenAiSelfHarmInstructionsImageSignal

Plus matching SignalType enum entries, integrationForSignalType cases,
SignalArgsByType / RuntimeSignalArgsByType entries, IoC registration,
and the two new category names in OpenAiModelName +
OpenAiImageModelName.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Expose self-harm/intent + instructions signals to GraphQL + client

Same shape as the previous fix on PR #534: the four new self-harm
subcategory signal types need to round-trip through both
hand-maintained mirrors or the dashboard can't see them.

- server/graphql/modules/signal.ts: add the four
  OPEN_AI_SELF_HARM_{INTENT,INSTRUCTIONS}_{TEXT,IMAGE}_MODEL types to
  the SDL enum.
- client/src/models/signal.ts: add the same four to the OpenAi case
  in integrationForSignalType.
- Regenerate codegen.

The coverage test added in PR #534 catches this regression class on
new SignalType additions; it now exercises 33 enum values (was 29).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add docstrings to self-harm/intent + instructions signal classes

Matches the docstring style applied to the base PR's image signals.
Raises this PR's docstring coverage above CodeRabbit's 80% pre-merge
threshold by documenting each of the four new signal classes
(self-harm/intent text+image, self-harm/instructions text+image) and
their run() methods.

No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Apply factory pattern to self-harm subcategory signals

Mirrors the cleanup from PR #534: the 4 self-harm subcategory signal
classes (2 text + 2 image) collapse to ~20-line factory calls instead
of ~110-line class definitions each.

- Adds `makeOpenAiTextModerationSignal` alongside the existing image
  factory introduced in #534.
- Both factories now share a single internal `makeOpenAiModerationSignal`
  parameterized by input scalar + run impl, so the class body lives in
  one place (was duplicated twice in #534's first cut).
- Factories moved into `openAiModerationSignalFactory.ts` — keeps
  `openAIModerationUtils.ts` under the 500-line lint limit (the second
  factory pushed it to 510) and organizes the file boundary cleanly:
  utils = lib functions, factory = class generator.

The 4 self-harm subcategory signal files (text + image variants for
`self-harm/intent` and `self-harm/instructions`) now just import the
appropriate factory and pass a config object.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix bad import

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Juan Mrad <juansmrad@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the manual review “skip” flow so skipping is handled as a single server-side operation and so dequeueing can transparently step past jobs a given reviewer recently skipped (without hiding those jobs from other reviewers).

Changes:

  • Added per-reviewer skip tracking in Redis and updated dequeue to hold skipped jobs aside under the reviewer’s lock, then release them back to the shared pool.
  • Updated dequeue behavior to avoid long-poll hangs by using block: false when calling BullMQ getNextJob.
  • Simplified the client skip flow to a single logSkip mutation and improved UX when no reviewable job remains.

Reviewed changes

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

Show a summary per file
File Description
server/services/manualReviewToolService/modules/QueueOperations.ts Implements per-reviewer skip filtering/hold-aside behavior and Redis skip-set helpers; updates getNextJob usage.
server/services/manualReviewToolService/modules/QueueOperations.reviewerSkips.test.ts Adds coverage for skip-set recording/expiry and per-reviewer dequeue filtering behavior.
server/services/manualReviewToolService/manualReviewToolService.ts Makes logSkip also record the per-reviewer skip and release the lock server-side.
client/src/webpages/dashboard/mrt/manual_review_job/ManualReviewJobReview.tsx Removes separate ReleaseJobLock mutation and updates skip UX/redirect behavior.
client/src/graphql/generated.ts Removes the generated ReleaseJobLock operation artifacts.
CHANGELOG.md Adds an unreleased “Review Console” changelog entry for the new skip behavior (needs scope correction).

Comment on lines 779 to 795
const skipToNextJob = async () => {
// First, release the lock on the current job and log the skip
// Skipping is one server-side operation: it logs the skip, hides the job
// from this reviewer for the skip window, and releases the lock so the
// job returns to the shared pool for everyone else.
if (queueId && job?.id && lockToken) {
await Promise.all([
logSkip(),
releaseJobLock({
variables: {
input: {
queueId,
jobId: job.id,
lockToken,
},
},
}),
]);
const result = await logSkip();
if (result.data?.logSkip !== true) {
// Nothing was released or hidden; stay on the current job so the
// reviewer can retry (or decide) instead of advancing past it.
setModalInfo({
visible: true,
modalBody: 'Failed to skip this job. Please try again.',
footer: [{ title: 'Ok', type: 'primary', onClick: hideModal }],
});
return;
}
}
Comment on lines +1363 to +1369
// Release the held-aside jobs so other reviewers can pick them up
// immediately. This reviewer stays excluded via the skip set.
for (const held of heldAside) {
await this.releaseJobLock({
orgId,
queueId,
jobId: held.data.id,
Comment on lines +1348 to +1357
if (decision !== undefined) {
await this.removeJob({
orgId,
queueId,
lockToken,
jobId: convertedJob.data.id,
}).catch(() => {});
// then continue while loop
} else {
// this is the most likely case, where there is a job
Comment thread CHANGELOG.md Outdated
Comment on lines +5 to +9
## Review Console

- Added per-queue job sort modes for manual review: FIFO (default, unchanged), most-reported-first, and custom weighted — ordering is computed at enqueue via BullMQ job priority, so the locked dequeue path is unchanged (#718)
- Added org-configurable job priority weights (Settings → Job Priorities) for weighted queues; changing weights or a queue's sort mode re-sorts already-queued jobs in a background sweep (#892)
- Skip is now per-reviewer: a skipped job is hidden from that reviewer for 30 minutes while returning to the shared pool immediately; entering a drained or fully-skipped queue redirects to the queue list (#893)
…r-skip

# Conflicts:
#	CHANGELOG.md
#	client/src/graphql/generated.ts
#	server/services/manualReviewToolService/modules/QueueOperations.ts
@calebmcquaid
calebmcquaid requested a review from a team as a code owner August 7, 2026 03:41
@calebmcquaid
calebmcquaid requested a review from taobojlen August 11, 2026 05:33

@taobojlen taobojlen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Image

i think this needs a rebase 😅 this is too big to review and includes lots of unrelated changes!

github's native stacks feature might be useful!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants