The Invariant
Reads never touch the write path’s authority. Every answer is computed from committed facts at request time — manifest SELECT queries on the edge read local biz_* rows under RLS; /tenant/* on the Control Plane reads what the signed ingest channel committed. There is no cache to invalidate and no aggregate to trust: the answer you get is the state of the facts.
The Contract
Edge (operator session): the manifest data_link_engine declares SELECT queries as named triggers — e.g. LIST_LOTTI in the production-line vertical. They go through the same /api/v1/actions/:trigger endpoint, same RBAC, same idempotency — only the query shape differs (type: "SELECT" returns rows instead of writing).
Cloud (portal session or full device key): /api/v1/tenant/* — usage snapshot, hash-chained audit export, audit-webhook registry, import-jobs, invoices, Stripe portal link. Business projections are not hard-coded: they are whatever the vertical manifest declares.
The Flow
edge reads: LIST_LOTTI → SELECT … FROM biz_lotti WHERE tenant=session (RLS + SQL guard)
cloud reads: /tenant/usage → tenant + subscription + fleet + metering snapshot
/tenant/audit → NDJSON cursor export of cp_admin_audit (since_seq)
/tenant/audit/verify → re-walk the hash chain → {ok, checked, head}
Payload Example
# Edge — a declared read query, same endpoint family as mutations
curl -X POST http://localhost:8291/api/v1/actions/LIST_LOTTI \
-H "Authorization: Bearer $TOKEN" -H "X-Idempotency-Key: $(uuidgen)" \
-d '{"payload":{"lot_id":"LOT-2026-091"}}' | jq '.rows'
# Cloud — the badge surface: one snapshot for contract + fleet + metering
curl -s https://cp.axiom-trace.eu/api/v1/tenant/usage -H "Authorization: Bearer $PS" \
| jq '{plan: .subscription.plan, edges: .edges|length, events_month: .monthly_events}'
# SIEM export — incremental, cursor-paginated on seq
curl -s "https://cp.axiom-trace.eu/api/v1/tenant/audit?since_seq=$LAST&limit=1000" \
-H "Authorization: Bearer $PS"
import httpx
c = httpx.Client(base_url="https://cp.axiom-trace.eu",
headers={"authorization": f"Bearer {token}"})
v = c.get("/api/v1/tenant/audit/verify").json()
assert v["ok"], f"audit chain broken at {v.get('brokenAt')}"
Known Failures
| Symptom | Cause → fix |
|---|---|
403 with valid token |
RBAC — the role lacks the read trigger in permissions, or CORS portal_origin binding (tenant A’s session from tenant B’s origin) |
| Empty result, data “exists” | Wrong tenant_id/filter scope — reads are RLS-truthful, widen the filter |
400 on since_seq |
Malformed/negative cursor — the exporter never silently rewinds to 0 |
| Cloud data looks stale | The edge hasn’t flushed yet — check sync/ingest drain and the node’s last_poll_at heartbeat |
| Missing vertical endpoint | Not declared in the manifest — /tenant/* only covers contract/audit/billing; business data is manifest-declared |