Commit Graph
1 Commits
Author SHA1 Message Date
Yogesh OjhaandClaude Opus 5 7781259c62 feat(exports): run an export as a job, and keep the recipe
POST /exports saves what the table was showing — dimension, scope, the
filter body, the format — and queues a run. The row is the recipe, so
rerun runs it again against today's rows rather than handing back
yesterday's file, which is the point of keeping it.

The scope resolves when the run starts, not when it was saved: a scan
export reads that scan, a target or project export re-picks the newest
covering scan of each target, so a saved export follows the estate.
That needed a sync resolver, since surface_scope is async and the api is
not on the worker's path.

A bundle writes one file per dimension into a zip. Expiry is its own
status rather than a failure wearing the wrong label, and a reaper fails
a run the worker lost, which reports still lack.

Streaming takes a connection of its own. yield_per opens a server side
cursor, and the progress commit — or the per chunk join — executed on
the session would invalidate it mid-file; the first large export failed
with "named cursor isn't valid anymore" and nothing smaller ever would
have. The export therefore reads committed rows, so its promise test
moves to the durable fixture.

Proven end to end: a filtered CSV that matches the table, 7,784 URLs one
per line, a five file zip, a project wide export across 2,184 addresses,
and a rerun.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LYZfaNjq3Ym8GPvWYuHisS
2026-09-13 08:21:50 +05:30