Built for the things
that go to procurement.
The framework an enterprise team can adopt has to pass a longer list of questions than a startup's: how does multi-tenancy work, where do audit events go, can we self-host, does it run on our managed Postgres, what's the cluster story, how do traces flow. Voltro answers each of them with a primitive, not with a TODO. Built on Effect-TS — the same runtime Microsoft, Block, and Discord run in production today.
# values-prod.yaml — Voltro's Helm chart, real cluster.
voltro:
image:
repository: registry.acme.com/voltro/acme
tag: v1.0.0
replicas: 6
postgres:
embedded: false # Use the managed RDS / Cloud SQL
url: ${POSTGRES_URL} # Pulled from a sealed Secret
readReplicas:
urls: ${POSTGRES_REPLICA_URLS} # Comma-separated
regions: us-east-1,us-west-2
cache:
backend: redis # ElastiCache cluster
url: ${REDIS_URL}
observability:
otelEndpoint: http://otel-collector.observability:4318
serviceName: acme-app
session:
secret: ${VOLTRO_SESSION_SECRET} # From sealed Secret
audit:
sink: console # Pipe stdout to Datadog / Splunk
# kubectl apply -f values-prod.yaml --namespace=acme-prodSix requirements, six built-in answers.
Effect-TS production-grade internals
Voltro is built on Effect-TS — the runtime Microsoft Azure, Block, Discord, and others use in production today. Structured concurrency, typed errors, durable execution, OpenTelemetry — all features of the underlying platform we rely on, not features we re-implement.
Postgres-first, multi-dialect-tolerant
Postgres is the recommended primary store; MariaDB / MySQL / MSSQL / SQLite are first-class for enterprise procurement constraints. Read replicas, region-aware routing, RYW (read-your-writes) consistency policy — all opt-in env-var flags.
Row-level isolation via tenant mixin
The tenant() schema mixin AND-merges tenantId into every read at the runtime layer. Cross-tenant access is impossible by construction — not by a hand-rolled WHERE clause that someone forgot.
Full audit log
@voltro/plugin-audit records every mutation invocation (tag, subject, input, outcome, duration) to a configurable sink — console for dev, custom Effect for "pipe to Splunk/Datadog/SOC2 evidence". Schema-level audit() mixin adds createdAt/updatedAt/createdBy/updatedBy → actors on every row.
OpenTelemetry, end-to-end
Every primitive emits OTel spans: client.mutation, server mutation, store.transactional, action, subscription delivery, webhook. One traceId rides from React useMutation through to your downstream API call. Helm chart ships an OTEL_EXPORTER_OTLP_ENDPOINT wiring path.
Self-host today; managed cloud coming soon
The Helm chart, the Docker compose stack, the systemd baseline — all in your repo, all under your operations team. Voltro Cloud will be a managed deploy of the same primitives for teams who don't want to run the infra themselves — it's coming soon. Either way, engineering doesn't pick between two stacks.
Interop with the infrastructure you already run
A Voltro backend consumes and produces against your EXISTING Kafka (@voltro/plugin-queue: Schema-decoded consumers, serial per partition, retry + dead-letter; transactional producing through the outbox), mirrors table changes outward to it (cdc-out kafkaSink), and serves opt-in procedures to generated gRPC clients — the .proto emitted from your effect/Schema with checked-in field-number stability, guards and typed errors identical to the socket. Adjacent systems keep their contracts — adoption does not start with a migration of everything around it.
API versioning with a declared sunset flow
A REST route can exist in several versions side by side — defineRestRoute takes a first-class version field (/v1, /v2 as separate descriptors, no transformation DSL), the deprecation headers and the 410 tombstone name the version and its replacement, and the OpenAPI document lists every version with a grouping tag. Consumers migrate on your published schedule, not on your deploy day.
The framework doesn't fight your platform team.
Enterprise platform teams have already standardised — on managed Postgres, on a k8s cluster, on a centralised observability stack, on a sealed-secret tool. A framework that requires its own database, its own scheduler, its own observability vendor, its own auth flow IS the integration project. Voltro is the framework that consumes what your platform team already runs.
The compliance-friendly story:
- • Auth. @voltro/plugin-auth ships HttpOnly session cookies signed via HMAC-SHA256 with a hardcoded algorithm (no JWT-alg-confusion). API keys via apiKeyStrategy with SHA-256 hash lookup. JWT bearer via JWKS via jwtBearerStrategy.
- • RLS. The tenant() schema mixin enforces row-level isolation at the runtime AND the DDL. SOC2 / ISO 27001 auditors get a clear "rows are partitioned by tenant" story without a custom Postgres RLS policy.
- • Audit log. Every mutation lands in the audit sink. Pipe to your SIEM. The schema mixin stamps WHO + WHEN on every row. "Who edited this customer record three months ago" is a one-query answer.
- • Data residency. Multi-region read replicas with DB_REPLICA_REGIONS routing. Pin reads to in-region replicas. Writes always to the primary.
- • Observability. Trace ID propagates frontend → API → downstream. Every log line carries it. Every span carries it. Audit failures and look at the WHOLE causal chain with voltro logs --trace.
What a request passes through before your handler.
- 1
The credential becomes a subject
Session, API key or bearer token — or none, in which case the caller is anonymous rather than trusted. Revocation is checked here, not at the first query.
- 2
Authorisation runs on the declaration
Guards sit on the procedure, so every entry point that can reach it goes through them. A rule written inside a handler is one a second entry point bypasses.
- 3
The tenant predicate is merged in
Reads are bounded to the caller's tenant by the runtime, not by a filter each query remembers. Widening it at all is an explicit, named operation.
- 4
The work is recorded as it happens
The mutation lands in the audit sink with its subject and outcome, the row carries who changed it and when, and one trace identifier ties the whole chain together.
The questions a security review actually asks.
How is tenant isolation enforced, and what could break it?
Tenancy is a schema mixin, and the runtime merges the tenant predicate into every read rather than trusting each query to add one. That distinction is the whole argument: a hand-written filter is correct until someone writes the query that forgets it, and the forgotten one is never the query anyone reviews.
The enforcement is deliberately hard to disable by accident. Where the boundary can be relaxed at all, it is an explicit, named operation rather than an ambient default, and the framework's own paths go through the same gate as application code. A rule the framework exempts itself from is a rule that will be exempted elsewhere.
That design came out of a real defect: state that decided access lived module-locally and split per instance, so several read paths saw an empty filter and returned the whole tenant. The fix was to make the state process-global and to make an unset value unambiguous rather than permissive — the general lesson being that fail-open defaults are the failure mode worth engineering against.
What evidence does the system produce for an audit?
Mutation invocations land in an audit sink — the tag, the acting subject, the input, the outcome and the duration — and the sink is configurable, so the records can be piped into whatever SIEM or evidence store the organisation already runs. Audit is not a log format you have to reconstruct after the fact.
Row-level provenance is separate and complementary. An audit mixin stamps created and updated timestamps together with the acting subject on every row, which turns questions like who changed this record and when into a single query rather than a log-search exercise across retention windows.
Traces tie the two together. One trace identifier rides from the browser call through the server mutation, the database transaction, any background work it triggers and the outbound webhook, so a failure can be examined as one causal chain instead of four correlated systems.
Can this run on our platform, or does it bring its own?
It consumes what your platform team already runs. Postgres is the recommended primary store, with MySQL, MariaDB, SQL Server and SQLite supported first-class because procurement constraints are real and rarely negotiable. Read replicas and region-aware read routing are configuration, not a fork.
There is no mandatory broker, workflow service or cache tier. Durable workflows journal to the database, scheduled jobs coordinate through it, and real-time fan-out uses the database's own change feed on the engines that provide one. Each of those can be pointed at dedicated infrastructure later, but none of them is a prerequisite for a first deployment.
Deployment baselines ship for Compose, Helm and a plain systemd target, and observability exports over OpenTelemetry to whatever collector already exists. The framework's position is that a platform team's standardisation is an asset to use, not an obstacle to route around.
What does upgrading and long-term maintenance look like?
Schema changes are declarative. The schema file is the desired state, the planner diffs it against the live database, prints the operations it intends and classifies the destructive ones — so a migration review is a review of intent rather than an archaeology of hand-written change scripts.
Framework breaking changes ship codemods rather than upgrade prose. `voltro update` bumps the packages, aligns peer versions and rewrites the affected source, which is what keeps a large estate from freezing on an old version because the migration cost exceeded the appetite for it.
One caveat worth stating plainly, because it has bitten in production: configuration that changes the declared schema must match between the process that applies migrations and the process that serves traffic. A migration job with different flags than the pod produces a green apply and a crash loop, which is why the checks derive the expectation instead of asking someone to keep two environments in step by hand.
Requirement to mechanism.
| Requirement | Mechanism |
|---|---|
| Tenant isolation | Schema mixin with runtime-enforced predicate merging on every read, not per-query filters. |
| Authentication | First-party sessions, MFA, passkeys and API keys; SSO and SCIM provisioning as plugins. |
| Authorisation | Declarative guards on every entry point plus role and scope checks, evaluated server-side. |
| Auditability | Mutation-level audit sink plus row-level actor and timestamp stamping. |
| Data residency | Region-aware read routing across replicas; writes to the primary; self-hosted throughout. |
| Observability | OpenTelemetry spans across client, server, database and background work under one trace. |
Frequently asked questions
Do we have to use your cloud?
No. Self-hosting is the default and the fully supported path — your infrastructure, your database, your network boundary. Nothing calls out to us at runtime, and the managed cloud is a separate, optional product.
Which databases are actually supported?
Postgres is recommended; MySQL, MariaDB, SQL Server and SQLite are first-class, exercised by dialect-parity tests rather than assumed compatible. Some capabilities, notably database-driven real-time fan-out, differ by engine and the docs state where.
How does SSO fit in?
Single sign-on and directory provisioning are plugins that sit alongside the built-in session layer, so an application can start with local accounts and add enterprise identity when a customer requires it, without re-architecting its authorisation model.
What is the Effect dependency going to cost our team?
It is the reason typed errors, structured concurrency and durable execution are properties of the runtime rather than things we re-implement. Everyday application code reads like ordinary async code; the depth is there when a problem needs it.
How do we evaluate it without a procurement cycle?
Scaffold a project against a disposable database and run it locally — nothing requires an account, a licence server or an outbound connection to evaluate. Licensing is per developer seat and applies when you build with it, not when you read it.
The features your security review will ask about.
Multi-tenancy
Schema-mixin, runtime-enforced. The clean answer to the "tell us about cross-tenant isolation" section of the security questionnaire.
Durable workflows
No Temporal, no Inngest — internal-only Effect-TS infrastructure. Procurement doesn't need to onboard a third vendor.
Type safety
Branded TypeIDs, typed errors, no codegen step. Type-level guarantees translate to fewer runtime-only bugs.
Open the framework. See it for yourself.
Every primitive on this page is in the framework today. Clone the starter, run `voltro dev`, and have it on screen in two minutes.