feat: downloads of saved views and saved reports (phase c) #7

Merged
KofTwentyTwo merged 10 commits from feature/phase-c-downloads into develop 2026-10-14 14:17:29 +00:00
KofTwentyTwo commented 2026-10-14 03:17:19 +00:00 (Migrated from github.com)

Phase (c) of docs/DESIGN.md §5. Stacked on #6 → #5, and it needs QRun-IO/qqq#1170.

  • views/<slug>.<csv|xlsx|json>. The saved view's filter, columns and order are exported as the user via POST /table/{t}/export, streamed with invokeForStream.
  • reports/<slug>.<fmt>. renderSavedReport runs as the user, and the file is then streamed from GET /download/{file}. reports/ is listed only when the instance has a report storage table.
  • No disk. A generated read channel serves the stream through a bounded in-memory read-ahead window (downloadReadAheadMb). A backward seek past the window fails with "download sequentially".
  • Limits. maxGeneratedFileMb and the account's daily download cap are counted as the bytes flow, and an overrun logs LIMIT.
  • Audit. DOWNLOAD transfer-log rows record the object kind, id, format, bytes and duration.
  • Masking. Downloads carry exactly what /qqq/v1 returns for the user. The test uses a FieldSecurityLock DENY field and byte-compares the SFTP file with a direct same-user export. The secured-data tiers plug into the same core path.
  • Spec note. Core's RenderSavedReportExecuteStep stages the rendered report in a JVM temp file and deletes it in try-with-resources. That is the bounded temp area the owner rule allows, so the k8s temp emptyDir is now 512Mi in-memory.

Tests: 77, covering:

  • CSV, XLSX and JSON;
  • column order;
  • the masking parity;
  • no access without read permission;
  • a saved report run as the user;
  • a 50k-row streaming export under the no-disk guard;
  • the read-window unit tests;
  • the quotas;
  • audit rows.

JaCoCo is at 92.6% instructions and 97.2% classes.

🤖 Generated with Claude Code

Phase (c) of `docs/DESIGN.md` §5. **Stacked on #6 → #5**, and it needs QRun-IO/qqq#1170. - **`views/<slug>.<csv|xlsx|json>`.** The saved view's filter, columns and order are exported **as the user** via `POST /table/{t}/export`, streamed with `invokeForStream`. - **`reports/<slug>.<fmt>`.** `renderSavedReport` runs as the user, and the file is then streamed from `GET /download/{file}`. `reports/` is listed only when the instance has a report storage table. - **No disk.** A generated read channel serves the stream through a bounded in-memory read-ahead window (`downloadReadAheadMb`). A backward seek past the window fails with "download sequentially". - **Limits.** `maxGeneratedFileMb` and the account's daily download cap are counted as the bytes flow, and an overrun logs `LIMIT`. - **Audit.** DOWNLOAD transfer-log rows record the object kind, id, format, bytes and duration. - **Masking.** Downloads carry exactly what `/qqq/v1` returns for the user. The test uses a `FieldSecurityLock` DENY field and byte-compares the SFTP file with a direct same-user export. The secured-data tiers plug into the same core path. - **Spec note.** Core's `RenderSavedReportExecuteStep` stages the rendered report in a JVM temp file and deletes it in try-with-resources. That is the bounded temp area the owner rule allows, so the k8s temp `emptyDir` is now 512Mi in-memory. **Tests:** 77, covering: - CSV, XLSX and JSON; - column order; - the masking parity; - no access without read permission; - a saved report run as the user; - a 50k-row streaming export under the no-disk guard; - the read-window unit tests; - the quotas; - audit rows. JaCoCo is at 92.6% instructions and 97.2% classes. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.