Repository navigation
Description
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:
parseDateEntryusesnew 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 like6/22/2026is read in the browser's local time zone, and6/22/27becomes 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
- 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.dateand$CreateSessionData.date. The server then rejects the request with a 422 that gives the row index. - 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.
- 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).
- Optional: accept only ISO
YYYY-MM-DD(or full ISO datetime) in the CSVdatecolumn, so that the parsing does not depend on the browser.
Each change needs a unit test and an e2e test.
Activity