Reporting contract
Praxis has one contract for turning fleet data into a downloadable, auditable
report. This document is the human-readable companion to the machine-readable
catalog served at GET /reports/catalog.
Two tiers
- Table export (shortcut): downloads the visible table (current page + filters) as CSV/JSON. A convenience only; not recorded as a report run, not schedulable. Lives on data pages (Systems, Package Inventory, Job History, Config Audit, System Comparison, Access Review CSV).
- Report generation (contract): a named report kind with a defined
scope, filters, and format. Generating one materializes rows, streams a
bounded download, records a durable
report_runsrow, and is schedulable. Manual and scheduled runs go through the same report-kind contract.
The report-kind contract
Source of truth:
- Vocabulary:
report_run_service.VALID_REPORT_KINDS(mirrored by thereport_runs/report_schedulesreport_kindCHECK constraints). - Catalog (labels, formats, scopes, filter keys):
report_catalog.py, served atGET /reports/catalog. A fail-closed module assert keeps the catalog and the vocabulary from drifting apart. - Manual materialization: each domain’s
*_export_service.collect_export_rows(package posture:package_reports_export_service). - Scheduled materialization:
report_schedule_service.DISPATCH_MAP(same collectors; the scheduler records a row-count run). - Run recording:
report_run_service.safe_record_completed_run(never breaks a download if persistence fails).
Every report kind records a report_runs row with: report_kind,
triggered_by (user | system_scheduled), format, filters_snapshot
(scope + filters), row_count, state, actor, and timestamps.
Supported report kinds (1.0)
| Report kind | Category | Formats | Scope / filters |
|---|---|---|---|
package_outdated | package | csv, json | fleet, smart_group, system; security_only, name_filter |
package_compliance | package | csv, json | fleet, smart_group |
patch_executions | patch | csv, json | fleet, date range; plan_id, state |
patch_update_plans | patch | csv, json | fleet, date range; policy_id, state |
patch_reboot_queues | patch | csv, json | per execution_id |
patch_rollback_runs | patch | csv, json | per execution_id |
compliance_evidence | compliance | jsonl, csv | fleet, system, date range; policy_id, verdict |
compliance_remediation_requests | compliance | csv, json | fleet, system, date range; policy_id, state |
compliance_remediation_plans | compliance | csv, json | fleet, system, date range; policy_id, state, current_only |
compliance_remediation_executions | compliance | csv, json | fleet, system, date range; policy_id, state |
RBAC & fleet scope
- Report generation:
require_role("admin", "maintainer"). Report-run / schedule reads: admin/maintainer/auditor. Schedule mutations: tenant-wide admin only (+REPORTS_SCHEDULED_EXPORTSentitlement). - Fleet scope is enforced before any rows are materialized: a scoped caller
sees only in-scope systems; an explicit out-of-scope
system_idis a non-disclosing 404; an empty scope yields an empty export. No out-of-scope system ids, hostnames, package names, counts, or schedule metadata leak. - Credential secrets are never exportable.
Bounding
Exports cap the review window (366 days) and row count
(_export_helpers.EXPORT_MAX_ROWS = 50_000), and chunk queries with
yield_per. Compliance evidence streams row-by-row. Oversized requests are
rejected with HTTP 422; narrow the window/filters and retry.
Surfaces that export without being report kinds
These download data but are not report kinds, so they record no run, carry no manifest, and cannot be scheduled:
- Fleet inventory (Systems):
GET /export/systems(table export). - Package inventory:
GET /export/packages(table export). - Job and automation history:
GET /export/jobs(table export). - Audit events:
GET /export/audits(table export). - System comparison:
GET /systems/compare/export(table export). - Access reviews:
GET /access-reviews/{id}/export.csv(table export). - Session recordings:
GET /recordings/{id}/cast(artifact download). - Content, mirror, and airgap history: read dashboards. Airgap bundles build
via
POST /airgap/exports, which has its own bundle lifecycle and is not a report run.
Report schedules carry their scope and filters as a raw JSON
filters_snapshot, validated for date keys and size rather than through
per-kind form controls. The report catalog (GET /reports/catalog) exposes each
kind’s filter_keys.
Out of scope: a custom report query language, PDF output, and any credential-secret export.