Skip to content

Bulk upload and record/session create accept future dates; only the Start Session form checks them #1887

Open

Description

@gdevenyi

Problem

The CSV bulk upload accepts a record date in the future, and so does every server path that creates records or sessions. The only future-date check is in the web Start Session form (StartSessionForm.tsx, .max(currentDate) → session.errors.assessmentMustBeInPast). The bulk upload does not use that form, so nothing stops a typo.

Observed

On a production instance (v2.5.2), 17 MOUSE_ORIGIN_FORM records were bulk-uploaded on 2026-06-22. Each record and its RETROSPECTIVE session were dated 2027-06-22: the year was mistyped in the CSV. The data stayed one year in the future for almost five months, until a manual audit found it. It had to be corrected with a direct database update, because PATCH /instrument-records/:id cannot change date and there is no session update endpoint.

Audit of the upload path

No layer checks that the date is not in the future:

Layer Location Check on date
CSV parsing (web) apps/web/src/utils/upload.ts parseDateEntry only "is a valid date" (new Date(entry))
Row schema (web) upload.ts, instrumentSchema.extend({ date: z.date(), … }), in both the Zod 3 and Zod 4 branches none
Request schema (shared) $UploadInstrumentRecordsData.records[].date = $DateLike (packages/schemas/src/instrument-records/instrument-records.ts) none
Service (API) InstrumentRecordsService.upload → SessionsService.createMany none

The single-record and session create paths have the same gap on the server: $CreateInstrumentRecordData.date and $CreateSessionData.date are plain $DateLike. Today the web form protects them, but a direct API client or a script does not get that protection.

Two related weaknesses in the same parser:

  • parseDateEntry uses new Date(entry), so the meaning of a value depends on its format. A date-only ISO string is read as UTC midnight, but a value like 6/22/2026 is read in the browser's local time zone, and 6/22/27 becomes 2027 without any warning.
  • The page shows no summary of the date range before the user submits, so a wrong year is easy to miss in a file of hundreds of rows.

Proposal

  1. Shared: add a schema for a record or session date that must not be in the future, for example $PastDateLike. Allow a small tolerance (for example the end of the current day in UTC) for clock skew. Use it for $UploadInstrumentRecordsData.records[].date, $CreateInstrumentRecordData.date and $CreateSessionData.date. The server then rejects the request with a 422 that gives the row index.
  2. Web upload: apply the same limit in the row schema of both Zod branches, and show the row number and the value in the error message. Before submit, show the earliest and the latest date found in the file.
  3. Do not limit date fields inside an instrument's own data. Some are in the future by design (for example a predicted birth date or an expiry date).
  4. Optional: accept only ISO YYYY-MM-DD (or full ISO datetime) in the CSV date column, so that the parsing does not depend on the browser.

Each change needs a unit test and an e2e test.

Activity

  1. added
    BugType: existing behavior is wrong
    Difficulty: MediumStraightforward change, about a day or less
    Area: APIapps/api (NestJS backend, Prisma models, CASL permissions)
    Area: Webapps/web (clinician-facing React SPA)
    Area: SharedCross-tier packages (schemas, react-core, etc.) not covered by another area
    on Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: APIapps/api (NestJS backend, Prisma models, CASL permissions)Area: SharedCross-tier packages (schemas, react-core, etc.) not covered by another areaArea: Webapps/web (clinician-facing React SPA)BugType: existing behavior is wrongDifficulty: MediumStraightforward change, about a day or lessPriority: MediumReal value; schedule normally

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions