platform/clickhouse/init/schema.sql.
Schema init runs once. The Docker image applies
init/schema.sql exactly
once, on first container start with an empty data volume — editing the SQL
afterwards is ignored on existing environments. To apply a schema change, either
make clickhouse-reset (wipes data) or connect with make clickhouse-cli and run
the migration SQL by hand. In production, treat schema changes as a deliberate
maintenance step against your ClickHouse cluster.How events arrive
The SDK does not write to ClickHouse, and neither does the API. Events travel as OpenTelemetry spans through three more services that a self-hosted stack has to run alongside the API:- Collector (
platform/collector/, Go): the OTLP/HTTP receiver. Verifies the bearer API key’s signature and revocation status, stamps the project id on every span as the Kafka record key, and publishes to Redpanda. A rejected key is a401back to the SDK; a self-declared project id in the span is never trusted. - Redpanda: two topics,
hexgate.otlp.raw(the buffer, 3-day retention) andhexgate.otlp.dlq(permanently rejected spans, 30 days). ClickHouse is the system of record; the raw topic only has to outlive an enricher restart. - span-enricher (
hexgate_api.jobs.enricher, same image as the API): a consumer that decodes each span by instrumentation scope into a decision, LLM-usage or ban-enforcement event, resolvesagent_version_idfrom Postgres, and batch-inserts into the three tables. Offsets commit only after ClickHouse acknowledges, so a crash replays the batch;event_idplusReplacingMergeTreecollapses the duplicates.
make collector-run and make enricher-run start the two processes
against the dev Redpanda (make redpanda-topics creates the topics). The
reverse proxy in front of a deployed stack must route the path /v1/traces to
the collector rather than the API; the reference compose, ports and proxy rules
are in platform/DEPLOY.md, and make platform-smoke STAGE=<stage> sends one
of every event type through a live stage and checks each row arrived.
Integration tests (pytest -m integration) round-trip real spans through this
whole chain — opt-in so the default make platform-api-test stays
offline-friendly.
Legacy HTTP ingest. The API still serves
POST /v1/audit/decisions,
POST /v1/audit/ban-enforcements and POST /v1/audit/llm-invocations, one
event per request with the same bearer. The SDK no longer calls them; they
bypass the collector and are slated for removal. Don’t build on them.Seed data
refund_customer, create_ticket) with a default role in a 5-minute window 10
days ago.