Runs: Surface actual on-instrument acquisition time (acquired_at) - #62
Merged
Conversation
Adds a nullable `acquired_at` column to `instrument_runs` populated by the watcher from `min(file_created_at)` over a run's files. The watcher sends it on POST and on PATCHes that introduce an earlier file; the API applies LEAST(coalesce(...), incoming) so the value can only move earlier. The list query default sort and date filter switch to `coalesce(acquired_at, created_at)` so backfilled runs (e.g. data discovered when a new instrument is first added) interleave with fresh runs by their true acquisition time. The run detail header now shows both "Run started" and "Reported" timestamps. Includes a one-time backfill that derives `acquired_at` from the existing `files.file_created_at` manifest for runs reported by older watchers. Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
drizzle's gte/lte rely on the column's PgColumn mapper to serialize a JS Date for the postgres-js driver. With a raw SQL fragment as the LHS that mapper is bypassed, so the driver received a Date and threw ERR_INVALID_ARG_TYPE on every list query that bound a date_from/date_to. Bind ISO strings explicitly and cast to timestamptz on the server, and add a regression test that calls buildRunListQuery with a date range. Co-authored-by: Cursor <cursoragent@cursor.com>
The `LEAST(coalesce(...), $1)` SQL in the POST race-conflict path and the PATCH `acquired_at` update bound a JS Date to a raw sql fragment, which hits the same postgres-js Date->string coercion bug as the date_from/date_to filter — same fix: bind the ISO string explicitly and cast to timestamptz. The integration regression test now hits the API via date_from/date_to (previously imported buildRunListQuery directly, which broke because integration test workers don't share the app's @/lib/db singleton). date_from/date_to query params are now exposed on GET /api/v1/instruments/:id/runs and GET /api/v1/instrument-runs, matching what the page-level RSCs already pass to buildRunListQuery. Co-authored-by: Cursor <cursoragent@cursor.com>
`buildRunCountSubquery` was the last query still anchored to created_at, so backfilled runs (acquired weeks ago, but reported today) were both showing up as the most recent activity and being counted in the this-week window. Switch to coalesce(acquired_at, created_at) like the dashboard, sidebar, and list query. Co-authored-by: Cursor <cursoragent@cursor.com>
The cell now renders coalesce(acquired_at, created_at), so the previous "Created" header was misleading — sorting and date-filtering use the run's actual on-instrument acquisition time, not the row's creation time. Adds a shared AcquiredColumnHeader (mirroring RawFileColumnHeader) with a tooltip explaining the fallback to created_at for older or Lambda-created runs, and wires it into all six per-instrument table variants and the dashboard table. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an
acquired_attimestamp oninstrument_runspopulated by the watcher from the minimumfile_created_atacross a run's files. This lets backfilled runs (data that already existed on the PC when a watcher first connects) appear with their true acquisition time instead of "now". The list query default sort, date filter, and dashboard time windows switch tocoalesce(acquired_at, created_at), and the run detail header shows both "Run started" and "Reported".Changes
instrument_runs.acquired_at TIMESTAMPTZplusidx_instrument_runs_active_acquired_at(partial expression index oncoalesce(acquired_at, created_at) desc). Migration0020_add_acquired_at.sqlincludes a one-time backfill fromfiles.file_created_at.POST /runsacceptsacquired_at, falls back tomin(detected_files[].file_created_at), and folds late watcher-vs-lambda race writes viaLEAST(coalesce(acquired_at, $1), $1).PATCH /runs/:idapplies the sameLEASTsemantics so the value can only move earlier;GET/PATCHresponses now exposeacquired_at.buildRunListQuerydefault sort anddateFrom/dateTofilter usecoalesce(acquired_at, created_at).lookupRunByNaturalKey,dashboard.ts(lastRunAt, 24h/7d windows), andsidebar.tsupdated likewise. MCPsearch_runssort enum extended.RunDetectortracksacquired_at_sentonRunState, sendsacquired_at(ISO UTC) on_report_new_run, and PATCHes only when a later-stable file reveals an earlier birthtime. Hydration seeds the cursor from the persisted manifest.row.acquired_at ?? row.created_atin the existing "Created" cell.RunHeadershows "Run started …" (when present), "Reported …", "Updated …".instrument-runs.test.tscovers explicit-POST, derived-from-files POST, earlier-PATCH, later-PATCH ignored, default coalesce sort. NewTestAcquiredAtclass intest_run_detector_hydration.pycovers the helper, POST payload shape, the earlier-PATCH path, and the monotonic no-PATCH path.Breaking changes
None.
acquired_atis nullable and all sort/filter call sites coalesce tocreated_atfor runs predating the watcher backfill or coming from the Lambda. Existing API callers that omitacquired_atare unaffected.Driveby changes
npm audit fixbump inweb-app/package-lock.json(0027c9d, unrelated to this feature).Testing
make check-all(ruff, pyright, prettier, eslint, tsc) — clean.make py-test— watcher unit tests, including newTestAcquiredAtcases.make fe-test—instrument-runs.test.tsintegration tests, including newRun acquired_atblock.0020_add_acquired_at.sqlagainst staging and spot-check that pre-existing runs haveacquired_atpopulated from the file manifest.acquired_atand that out-of-order stable files PATCH it earlier.