Playwright Tests / test (push) Canceled after 0s
- Updated the stored procedure `fds__admin_backupAndClearInvoiceFile.sql` to back up not only the invoice file but also JSON snapshots of related data (invoices, items, details, service requests) to ensure consistency and traceability. - Modified the table structure of `fds__invoice_file_backups` to accommodate new JSON columns for storing these snapshots. - Added an ADR (0030) document to outline the decision and rationale behind these changes, emphasizing the importance of maintaining a consistent set of data alongside the backed-up file.
3.4 KiB
3.4 KiB
status, date, applyTo, supersededBy
| status | date | applyTo | supersededBy | ||
|---|---|---|---|---|---|
| Accepted | 2026-09-17 |
|
0030 — Invoice-file backup carries a JSON snapshot of the invoice data
Context
ADR 0027 introduced [dbo].[fds__invoice_file_backups]: one row per
admin backup-and-clear action holding the invoice's [file] bytes before
they are cleared for regeneration (ADR 0025). The backup held only the
rendered PDF. The data it was rendered from — the fds__invoices row, its
fds__invoice_items rows, its fds__invoice_details row and its
fds__invoice_servicerequests link rows — lives on in
the live tables and keeps changing (payment status, modification stamps,
later edits). A backed-up file could therefore no longer be matched against
the data state it was produced from, which is exactly the question the
backup exists to answer when a regenerated document differs from the
original.
Decision
- Each backup row additionally stores four JSON snapshots, written in the
same statement and transaction as the file copy:
InvoiceJson— thefds__invoicesrow (FOR JSON PATH, WITHOUT_ARRAY_WRAPPER), without[file], which is already stored as bytes in the row and would otherwise be base64-duplicated;InvoiceItemsJson— allfds__invoice_itemsrows for the invoice as a JSON array, ordered bySortOrder,Position,Id;InvoiceDetailsJson— thefds__invoice_detailsrow, orNULLif the invoice has none;InvoiceServiceRequestsJson— allfds__invoice_servicerequestsrows for the invoice as a JSON array, ordered bySortOrder,Id. Not part of the rendered document, but part of the invoice's data set.
- The snapshots are JSON columns on the same backup row rather than
four mirror tables: one row is one consistent set (file + data), the
atomicity guarantee of ADR 0027 covers it without extra keys, and adding a
column to an invoice table does not require touching the backup schema.
The item/detail/service-request snapshots use
SELECT *, so new columns appear automatically; the invoice snapshot lists its columns explicitly only to exclude[file]— a newfds__invoicescolumn must be added to that list. - Restore is manual (
OPENJSONin SSMS); no app-side reader is added. The snapshots are forensic evidence, not a rollback mechanism.
Consequences
- Backup rows grow by the size of the invoice's data (small next to the PDF). No retention policy is added — same standing as ADR 0027.
INCLUDE_NULL_VALUESis used so aNULLin the snapshot is distinguishable from a column that did not exist at backup time.- Adding a column to
fds__invoicesrequires updating the explicit column list in the proc;fds__invoice_items/fds__invoice_details/fds__invoice_servicerequestsneed no change.
Alternatives considered
- Mirror tables (
fds__invoice_backups,fds__invoice_items_backups, …) keyed by backupuid: typed and queryable, but four schemas that drift with every invoice-table change and four inserts to keep atomic. Rejected for maintenance cost; the snapshots are read rarely and by hand. - Snapshot only the invoice header: the items are what the PDF body is rendered from; leaving them out would not answer "what did this file contain and why".