Tessera RP

PX-09

North Star

One system, drawn for two readers.

Engineers arrive wanting wire formats, table names, the tenancy mechanism and what happens when a source goes down. Finance and operations arrive wanting the value stream and the decisions it supports. Both drawings are here — pick a view.

Fig. 1

The stack, and what moves between layers.

Seven layers. Each one owns a job the layer above it is forbidden from doing, and the arrow between any two names the contract that crosses it.

L1 · Source

customer-owned

Your systems of record

Read where the data already is. Nothing is asked to change on your side.

8

connectors shipping today

15

more named on the roadmap

0

writes back into your ERP

Shipping · protocol

  • Dynamics 365 F&O · SQL

  • Business Central · OData v2

  • NetSuite · SuiteQL

  • Acumatica · REST

  • Shopify · GraphQL

  • Amazon · SP-API

Everything the ERP does not hold

  • CSV upload

  • Generic REST

  • SFTP drop

  • Google Sheets

L2 · Ingest

lib/connectors

Connector SDK + sync runner

Talks to source APIs. Deliberately knows nothing about the warehouse schema.

Resume

watermark cursors, never a restart

Skip

content-hash, so a re-run is cheap

Survive

backoff + jitter, Retry-After, DLQ

L3 · Landing

raw.record

Raw tier

Append-only JSONB, exactly as the source sent it.

1 table

not one per stream

Replayable

a mapping fix is a re-run, not a re-extract

Drift-proof

a new source field costs nothing here

Keyed by (tenant_id, connection_id, stream, source_id) with a JSONB payload and a content hash.

L4 · Canonical

wh.*

ERP-agnostic star schema

The interoperability boundary. Above this line, nothing knows which ERP it came from.

14

conformed dimensions

13

fact tables

7

cost columns on every order line

Product cost, inbound freight, duty, outbound freight, payment fee, marketplace fee and returns provision each get their own column — because an ERP books revenue and product cost and none of the other five.

landed_cost and contribution_margin are generated columns. Postgres computes them, so no query can derive them a second way.

L5 · Graph

In build

Tessera DB

Entities and the edges between them

Fed from the same canonical rows — it sits beside the star schema, not instead of it.

What it adds

  • Unbounded traversal

  • Multi-tier supplier exposure

  • Component-level recall

  • Entity resolution

  • Centrality · communities · cycles

Written as intent, not inventory. Everything above and below this layer is running.

L6 · Semantic

lib/semantic

Governed metric catalog + compiler

One definition per metric, and the only thing in the system permitted to emit SQL.

229

metrics across 14 domains

101

dimensions they can be cut by

1

definition each — no second opinion

L7 · Surface

app/*

What people actually use

Fifteen surfaces, every one of them resolving the same metric definitions.

Decide

11 persona dashboards, Explore, the assistant

Operate

inventory, procurement, stores, finance, vendors

Keep

alerts, saved views, governed reports, exports

Spans every layer

Four concerns that cannot live in one band, because they hold at every band.

Tenancy

app.current_tenant()

Every tenant-scoped table has RLS ENABLE + FORCE. Access goes through withTenant(), which sets the GUC transaction-local. A forgotten WHERE clause returns zero rows, not another customer's books.

Authorization

role rank · data_scope

owner 40 > admin 30 > analyst 20 > viewer 10 gates mutations. A membership row can carry {"channel":["DTC-US"]} and the compiler appends it as a mandatory filter — a caller cannot widen their own scope.

Sessions

SHA-256, never the token

The cookie holds an opaque random token; only its hash is stored. active_tenant_id lives on the session, so switching tenant is an update rather than a re-login. sso_subject is already on the user model.

Observability

requestId + tenantId

Structured single-line JSON with deep key-based redaction, on every request-scoped line. /api/health for liveness, /api/ready for database reachability.

Fig. 2

The one path to the database.

Every read path — a dashboard tile, the Explore UI, an alert evaluation, the assistant — produces the same object and hands it to the same compiler.

The invariant

Nothing writes SQL except the compiler.

1

QuerySpec

Tile, Explore, alert rule or assistant — all four emit the same object

2

zod validate

Shape and types, before anything is resolved

3

resolve vs catalog

Every id must exist in the governed catalog or it stops here

4

append scope

The caller's row scope is added after their filters, never before

5

emit SQL

Engine-neutral, params bound separately

There is no code path from model output to SQL text. The worst a prompt injection can produce is a QuerySpec — which is then checked against the catalog and against the caller's own data scope before it runs.

Fig. 3

Module boundaries.

A layering only holds if the modules are forbidden from knowing about each other. This is that contract, and it is enforced by the import graph rather than by convention.

ModuleOwnsMust not know about

lib/connectors

Talking to source APIs

The canonical schema

lib/transform

raw → canonical mapping

HTTP, metrics

lib/semantic

Metric definitions, SQL emission

React, HTTP

lib/analytics

Forecast, anomaly, cohort

The database

lib/personas

Declarative dashboard specs

SQL

lib/assistant

NL → QuerySpec

SQL

components/*

Rendering

The database

The connector layer deliberately does not know the canonical schema. That is what makes adding an ERP a contained change — implement the interface, write a mapper, done.

Fig. 4

What happens when something breaks.

Named failure modes with a stated response, because a data platform is judged on the day a source goes down rather than the day it does not.

Source API down

Backoff with jitter; cursor preserved; connection marked degraded after N failures

Source rate-limits

Honour Retry-After; resume from the persisted cursor

Record unmappable

Dead-letter row; the sync continues; surfaced in Data Admin

Schema drift

Raw tier unaffected; the mapper returns null → DLQ; alert on DLQ volume

Query too slow

statement_timeout → 504 carrying a narrow-your-range message

Query too large

LIMIT max+1 detects truncation; the result is flagged, never silently cut

Missing LLM key

The assistant degrades to deterministic matching; the product still works

What the layering buys you

Add an ERP, keep the work

A new source is a connector and a mapper. Every metric, dashboard and alert above Layer 4 carries over untouched.

One number, everywhere

Contribution margin resolves the same catalog entry on a tile, in an export, in an alert and in an answer. Two surfaces cannot disagree.

Swap the engine, keep the metrics

The compiler emits engine-neutral SQL. Postgres today; the adapter is the seam for what comes next.

Isolation that survives a bug

Row-level security is enforced by the database, not by application filters — so a mistake returns nothing rather than somebody else's books.

Tessera RP is live.

It runs at erp.par2labs.com, with a seeded demo workspace you can sign into without a password. Self-hosting is a Docker image and one command.