Primitive spec
Pelorat
The reporting-fork primitive. Pelorat forks operational writes into a query-shaped read plane — with backfill for history — so dashboards, analytics, and finance reports run against denormalised, query-friendly state without touching the operational hot path.
ReportingFork + ReportingForkDelivery + BackfillForkService), per-tenant PeloratTenantSeed, and now Report + ReportRun + ReportRunner for definitions and scheduled runs via Daneel step handlers. Report runs write durable output to S3 with a tenant-scoped download endpoint — the report viewer — so a run's result outlives the worker that produced it. Sink and query-shape adapters expand as report use-cases land.
What it owns
Pelorat owns the contract between the write side (every primitive's transactional state + audit outbox) and the read side (a separate, query-shaped store optimised for reports). It does not own the report queries themselves — those live with consumers — but it owns the durable fan-out and the backfill machinery that makes the read plane authoritative for historical queries.
Pelorat is not Palver. Palver pushes the live edge of the outbox to authenticated client sessions over WebSocket for instant UI. Pelorat materialises the same events into a query plane optimised for batch reads and aggregations. Same outbox, different delivery shape.
Concepts
- ReportingFork
- A named fan-out from the audit outbox to a query sink. Carries the source filter (which events fork here), the sink target, and the delivery policy (at-least-once vs. idempotent, batch size, retention).
- ReportingForkDelivery
- One delivery attempt of an event onto a fork. Tracks status (
pending,delivered,failed,retried) and any sink-side acknowledgment. The history is the audit trail of "what made it to reporting and when." - Sink
- The target plane — a denormalised table set, an OLAP store, a data warehouse, a Postgres reporting replica. Sinks are pluggable adapters that translate Foundation event envelopes into their native write shape.
- Backfill
- The mechanism for populating a new fork (or a re-derived one) from history.
BackfillForkServicewalks the audit outbox in order, producingBackfillResultrows with checkpoints so a long-running backfill resumes cleanly. - PeloratTenantSeed
- Per-tenant bootstrap. When a tenant comes online or migrates, the seed stands up the default set of forks and runs an initial backfill so the read plane is ready before the first report query.
- Query shape
- The Pelorat
Queryservices translate a small, deliberate vocabulary (entity + window + aggregation) into the sink's native query language. Report authoring talks to the query shape, not the underlying store. - Report viewer
- Report runs stopped being fire-and-forget: each run writes its output durably to S3 (the
PELORAT_REPORTS_BUCKET) and streams back through a tenant-scoped download endpoint, so operators open past runs from the Demerzel reports page instead of re-running them.
API surface
Endpoints will be versioned under /pelorat/v1/. Phase 1 ships the fork + delivery machinery and a console-driven backfill; HTTP query endpoints land as the query-shape vocabulary stabilises.
| Method | Path | Purpose |
|---|---|---|
| POST / GET | /pelorat/v1/forks | Create or list ReportingFork definitions. |
| GET | /pelorat/v1/forks/{id}/deliveries | Inspect delivery history and current lag for a fork. |
| POST | /pelorat/v1/forks/{id}/backfill | Kick off a backfill run for an existing fork. |
| GET | /pelorat/v1/queries/{name} | Run a named query against the read plane. Shape TBD. |
| POST / GET | /pelorat/v1/reports | Define or list Reports. |
| POST / GET | /pelorat/v1/reports/{id}/run · /runs | Trigger a run; list a report's runs. |
| GET | /pelorat/v1/reports/{id}/runs/{runId}/download | Download a run's durable S3 output, tenant-scoped. |
How it fits with the rest
flowchart LR
Live[Operational primitives] --> OB[(Audit outbox)]
OB --> Pe(Pelorat fork)
Pe --> Snk[Sink: warehouse / replica]
Snk --> Reports[Dashboards / reports]
OB --> Backfill[BackfillForkService]
Backfill -. seed history .-> Snk
OB -.also feeds.-> Sp[Speaker]
OB -.also feeds.-> D[Daneel]
OB -.also feeds.-> Pv[Palver]
Pelorat is one more subscriber on the same audit outbox Daneel, Speaker, and Palver already plug into. The cross-primitive contract is the same envelope every other consumer sees; the delivery target is a query-optimised store. Demerzel dashboards that need denormalised aggregates read from Pelorat sinks rather than walking the operational tables.