Skip to content

CollectiveX explorer backed by a lazy-ingest Neon database - #497

Merged
Oseltamivir merged 40 commits into
masterfrom
collectivex
Jul 29, 2026
Merged

CollectiveX explorer backed by a lazy-ingest Neon database#497
Oseltamivir merged 40 commits into
masterfrom
collectivex

Conversation

@Oseltamivir

@Oseltamivir Oseltamivir commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaces the /collectivex tab's just-in-time GitHub artifact serving with a dedicated Neon database that acts as a durable, lazily-populated cache of GitHub Actions. Sweep runs now outlive their 14-day artifact retention once viewed, the run picker lists up to 50 persisted runs (was 8, rebuilt per request), and runs are deletable from the dashboard.

Architecture

  • Lazy ingest-on-read: /api/v1/collectivex/{latest,runs,runs/[runId]} check the DB first; on a miss they discover completed CollectiveX Sweep runs (any harness branch — workflow identity only), download the cxsweep-matrix-* / cxshard-* artifact ZIPs in-request, and persist the raw documents. The shared reader (packages/db/src/collectivex/) assembles datasets at read time, so the evolving sweep JSON contract only ever touches the reader and applies retroactively to stored runs. No ingest workflow, cross-repo dispatch, or GitHub secrets.
  • Write safety: single-statement CTE inserts with ON CONFLICT DO NOTHING (concurrent first-viewers race safely; a partial run can never become visible); a FOR UPDATE-guarded refresh replaces a run's contents when GitHub reports a newer run_attempt (re-run of failed shards).
  • Deletion tombstones (cx_runs.deleted_at, docs freed) so re-discovery never resurrects a deleted run; the CLI (pnpm admin:db:ingest:collectivex <run>) pre-warms runs before artifact expiry, backfills, and un-deletes.
  • Resilience/caching: GitHub outages serve stored data; responses carry a dedicated collectivex CDN tag with a 60s freshness window; POST /api/v1/invalidate?scope=collectivex and run deletion purge only that scope.
  • Delete auth: DELETE /api/v1/collectivex/runs/[runId] uses a dedicated COLLECTIVEX_ADMIN_SECRET Bearer token (browser-held, independently rotatable — deliberately not INVALIDATE_SECRET), prompted once in the UI and remembered in localStorage.

Design rationale and invariants: docs/collectivex.md.

Deploy prerequisites (already provisioned)

Neon marketplace store inferencex-collectivex on this project; env vars DATABASE_COLLECTIVEX_READONLY_URL (readonly role @ pooled), DATABASE_COLLECTIVEX_WRITE_URL (owner @ direct), COLLECTIVEX_ADMIN_SECRET set for production + preview; migrations-collectivex/001 applied; 8 real sweep runs already ingested.

Validation

  • Unit: 427 db + 82 collectivex-scoped app tests (reader, artifact selection, lazy walker incl. tombstone/attempt-refresh/multi-attempt selection, queries, migration runner, routes incl. delete auth, api-cache scoping).
  • E2E: all 16 collectivex.cy.ts specs (fixtures mode), including the delete flow (token prompt, 401 → token cleared, declined confirm).
  • Live: lazy-ingested real runs from GitHub into local Postgres and the production Neon DB through both drivers (postgres.js + neon-http); verified tombstone survival across re-discovery, latest fallback after delete, GitHub-down fallback, and multibyte Authorization headers returning 401.
  • pnpm typecheck && pnpm lint && pnpm fmt clean; production build succeeds.

Note: unofficial-run overlay support does not apply — CollectiveX is not inference/evaluation chart data.


Note

High Risk
Introduces a new production data path (lazy GitHub ingest, separate DB writes, tombstoned deletes) and browser-stored admin auth; misconfiguration or ingest races could affect CollectiveX availability independent of the main dashboard.

Overview
CollectiveX stops serving sweep data directly from ephemeral GitHub artifacts and instead uses a separate Neon database as a durable cache filled on read by new /api/v1/collectivex/latest, /runs, and /runs/[runId] routes (raw docs stored; shared packages/db reader assembles responses). Runs survive 14-day artifact expiry, list progressively with discovery_complete polling, and fall back to stored rows when GitHub is down.

The /collectivex dashboard gains a multi-run table (visibility checkboxes, dash patterns in charts/legend), D3 explorer controls, matrix inventory, and admin DELETE via COLLECTIVEX_ADMIN_SECRET (Bearer, remembered in localStorage). POST /api/v1/invalidate?scope=collectivex and delete purge only the CollectiveX CDN tag; main blob cache is untouched.

Supporting changes: docs/collectivex.md, CollectiveX env vars in .env.example, admin:db:migrate:collectivex / admin:db:ingest:collectivex, Cypress fixtures + collectivex.cy.ts, tab nav/sitemap//zh/collectivex, and a Neon agent skill.

Reviewed by Cursor Bugbot for commit 93fe1af. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Jun 25, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
inferencemax-app Ready Ready Preview, Comment Jul 29, 2026 11:29am

Request Review

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread packages/db/src/ingest-collectivex.ts
The DB is a durable cache of Actions artifacts, but discovery still ran on
every request: the routes are force-dynamic and call ensure* per request, and
the module shared only the in-flight promise, so even a fully warm page spent a
`list workflow runs` call just to conclude "already known". Cost scaled with
traffic instead of with how often a sweep actually lands, adding GitHub latency
to every response and burning rate limit for nothing.

Give the two walking paths a 60s cooldown. ensureCollectiveXRun is left alone:
it returns on a DB hit before touching GitHub, and throttling it would keep
answering 404 for a run that had just appeared.

Failures are remembered too, for a shorter 10s window, and rethrown rather than
swallowed. Dropping them would let an outage read as "discovery fine, nothing
found", which turns the routes' 502/503 into a 404 whenever the DB has no
fallback row — that branch keys entirely off whether ensure* threw.

The cooldown is module state that outlives a test case, and the suite does not
reset modules, so resetCollectiveXDiscoveryCooldown() is exported and called in
beforeEach; without it every test after the first would be served from the
cooldown, issue no requests, and pass vacuously. Adds cover for warm-skip,
expiry, and the remembered-failure identity, and folds the repeated run-listing
fixture into one helper that documents why each call needs a fresh Response.
collectivex-latest.json carries one dataset payload — 3 series of 10 points —
but pretty-printed that is 3,384 lines for 60 KB of content, and it dominated
every diff that touched it (30% of this branch's insertions). Minifying both
API fixtures drops 3,405 lines and 51 KB with byte-identical parsed content.

These are machine-shaped payloads, replaced wholesale rather than hand-edited,
so the readability that indentation buys is not worth the diff weight.

Formatting has to be told to leave them alone: oxfmt reformats JSON as well as
TS, and the lefthook pre-commit job re-stages what it fixes, so the next commit
touching them would silently expand them again. oxfmt reads .prettierignore by
default, so the exclusion lives there (and would carry over if Prettier is ever
reintroduced).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Two red checks on this branch, unrelated to each other.

components/collectivex/types.ts re-exported the shared db types with
`export * from`, and Next's SWC loader rejects a star re-export anywhere in a
page's module graph ("Using `export * from '...'` in a page is disallowed").
The /collectivex page reaches this module through CollectiveXDisplay, so every
Cypress component spec failed at module build. Re-export the 18 types and 3
values explicitly.

.agents/skills/neon/SKILL.md was not oxfmt-clean, which fails `oxfmt --check`
repo-wide (it covers markdown, not just TS). Formatted, no content change.
Three findings from review, all the same shape: a caller-supplied value used
without validation in a context that gives it meaning beyond a string.

- fetchRunMeta interpolated repo and runId straight into an execSync command.
  Some callers validate, but the helper is exported and cannot rely on that, so
  it now rejects anything that is not `owner/name` and a digits-only run id.
- ingest-collectivex validated the repo slug but took INGEST_RUN_ID verbatim in
  the env-var path; only --download parsed a numeric id. Validation now sits
  where both modes converge, before any `gh api` call.
- selectShardArtifactNames interpolated runId into a RegExp, so a value with
  metacharacters changed what the pattern matched (CodeQL: regular expression
  injection) and could make the neighbouring `.+` backtrack badly. Escaped.

Reported by CodeQL (alert 22) and Copilot on #497.
CollectiveX responses were tagged with a bare `collectivex`, while the main
database tag is namespaced (`db:<CACHE_NAMESPACE>`). Deployments that share a
Vercel project share its cache tags, so purging `collectivex` in one
environment dropped every other environment's cached CollectiveX responses.

Add collectiveXCacheTag(), namespaced the same way, and use it for both the
response tag and the revalidateTag purge. COLLECTIVEX_CACHE_SCOPE stays bare on
purpose: it is the public identifier accepted by /api/v1/invalidate?scope=, not
a cache key. The route tests mock @/lib/api-cache wholesale, so they gain the
new export — without it the handler threw and the route answered 500.

Reported by Copilot on #497.
Migration 002 added run_attempt, but cx_run_docs stayed indexed on run_id
alone while every doc read filters `run_id = ... and run_attempt = ...` and
orders by id. A run holds one document per shard — 120 for a full 9-SKU sweep —
so the attempt filter and the ordering were resolved per row.

Index (run_id, run_attempt, id) so one index serves the filter and the sort.
Added as 003 rather than an edit to 002: the runner skips already-applied
files, so amending 002 would silently do nothing where it has already run.

Reported by Copilot on #497.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…gests

The CLI and the lazy ingest each carried their own copy of the attempt-selection
rule, and the copies had already drifted: the lazy one broke same-attempt ties on
the greater artifact id, the CLI one kept whichever it saw first. GitHub permits a
repeated artifact name within a run, and the reader keeps the first shard per
case_id, so the same run could resolve to different documents depending on which
path ingested it.

Generalise the db helper to selectShardArtifacts<T extends {name, id?}>, keeping
the id tie-break where callers have ids and first-match where they do not, and
have selectShardArtifactNames wrap it. The lazy ingest now calls that helper and
matrixArtifactName instead of re-deriving both, which also drops its private
MATRIX_PREFIX/SHARD_PREFIX copies and the unescaped run-id interpolation that
mirrored the pattern CodeQL flagged in the other copy.

Also fix a latent mislabel next door: measuredPoints ran Math.max() over an empty
row set for a success shard with no measurements, giving -Infinity, so every
ladder point compared "beyond the largest measured" and the case was reported as
a backend token-capacity wall that was never observed. Nothing measured now reads
as pending/not-measured.

Tests: same-attempt id tie-break, first-match without ids, a run id containing
regex metacharacters, the empty-row shard, and a scope test pinning the delete
statement to one run and the two CollectiveX tables.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread packages/app/src/app/api/v1/collectivex/runs/[runId]/route.ts Outdated
Comment thread packages/app/src/app/api/v1/collectivex/runs/route.ts
Comment thread packages/app/src/components/collectivex/CollectiveXDisplay.tsx
Comment thread packages/app/src/lib/collectivex-lazy-ingest.ts
Comment thread packages/app/src/components/collectivex/CollectiveXDisplay.tsx

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 2095ad6. Configure here.

Comment thread packages/app/src/lib/collectivex-lazy-ingest.ts
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.

3 participants