Report store decisions (STO)#
Decisions about how reports, versions and related entities are persisted across the two backends. For choosing and configuring a backend, see the backend install docs.
Primary code: reporting/services/report_store/.
STO-001 — The backend is transparent behind get_store()#
Applies to: report_store/__init__.py
Import from reporting.services import report_store and call through the
module. ReportStore (base.py) is the ABC; dynamodb.py and sql.py
implement it; REPORT_STORE_BACKEND selects one (default dynamodb).
Don’t: branch on the backend outside the store package. A caller that knows which store it is talking to is a caller that will drift when the other one changes.
STO-002 — The REPORT_LIST item is a full duplicate, not a pointer#
Applies to: _report_record, _report_record_items, _REPORT_RECORD_FIELDS
in report_store/dynamodb.py
Each report’s REPORT_LIST item duplicates its #METADATA item in full, and
list_reports/list_space_reports read only the duplicate.
Why it is dangerous: a field written to #METADATA but not to REPORT_LIST
fails silently — the report is correct when fetched by id and wrong in every
listing.
Do: add new report fields to _REPORT_RECORD_FIELDS so both copies are
built from one dict. Never write one copy at a time.
STO-003 — The spaces GSI is optional by design and keyed on an existing attribute#
Applies to: space_reports_index, _query_space_reports_via_index
space_id hash + SK range, ALL projection. It backs list_space_reports
and the delete_space emptiness check, and is queried with
begins_with(SK, "REPORT#") because the report #METADATA copy and sub-space
items also carry space_id.
Why optional: production tables are IaC-managed (DYNAMODB_CREATE_TABLE
defaults false), so a missing index must not be an error.
_query_space_reports_via_index returns None and callers fall back to
filtering the whole REPORT_LIST partition. The availability probe is cached
per process.
Why space_id rather than a dedicated attribute: keying on the attribute
that already exists is what makes the index need no backfill.
STO-004 — Alembic is the sole startup schema owner#
Applies to: report_store/migrations.py, reporting/migrations/versions/
SQLModelReportStore.initialize() upgrades to head under a pg advisory lock so
concurrent workers don’t race.
Every operation must be inspector-guarded. The baseline 0001 calls
SQLModel.metadata.create_all on a fresh database, so every later revision runs
against a schema that may already contain what it is adding. Verify against both
a fresh volume and an upgraded database. 0005_spaces.py is the current
template.
Note: SQLite dev DBs created before nullable user emails keep the old
NOT NULL. Recreate the DB to test emailless users on SQLite.