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.

Status: Phase 1 + 2 + 3 landed. Fork machinery (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. BackfillForkService walks the audit outbox in order, producing BackfillResult rows 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 Query services 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.

Pelorat endpoints in the Foundation API reference OpenAPI 3.1 schema for Pelorat with request/response shapes, parameters, and a try-it client.
MethodPathPurpose
POST / GET/pelorat/v1/forksCreate or list ReportingFork definitions.
GET/pelorat/v1/forks/{id}/deliveriesInspect delivery history and current lag for a fork.
POST/pelorat/v1/forks/{id}/backfillKick 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/reportsDefine or list Reports.
POST / GET/pelorat/v1/reports/{id}/run · /runsTrigger a run; list a report's runs.
GET/pelorat/v1/reports/{id}/runs/{runId}/downloadDownload 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.