A small, infrastructure-agnostic edge proxy that maps a versioned external contract onto a single evolving internal HTTP/JSON backend. Clients frozen at different points in contract-time keep working while the backend migrates freely.
pinned clients --versioned protobuf--> wavefront --current JSON/REST--> backend
|
+- descriptor bundle (one layer per contract version)
+- per-version resolution: route, or a transform shim
+- auth-transparent (forwards the bearer)
A pinned client is nearsighted in time: it sees an API only as it was the day
it shipped — app-store update lag, partners on last year's contract, a surface
on an old build. Without mediation, every internal change is hostage to the
oldest client in the wild, and teams accrete /v1 /v2 /v3 sprawl inside every
service.
By Fermat's principle, a signal crossing differing media takes the path of
least time and its wavefront reshapes to still arrive coherent. wavefront is
the version-mediation scheme, extracted: one declarative edge where external
contract variance is absorbed so the internal API stays singular.
- Bundle — the descriptor input: a directory of immutable per-version
layers (each a proto
FileDescriptorSet, the frozen OpenAPI, and the route binding) plus an operator-ownedresolution.yaml. Generated by the consumer, committed, loaded read-only. - Contract version — the external contract a client speaks; the selector that picks a transform profile.
- Transform — declarative request/response mapping for a version (field add / rename / optionalize / default / coerce; route remap).
- Adapter — a codec pair.
protobuf ↔ OpenAPI/JSONis the reference adapter, not the identity. - Selector — the key a request is resolved by. The contract version today; the engine is designed to extend to other selectors (codec, profile, tenant, cohort).
For depth, see docs/concepts.md.
It IS:
- A descriptor-driven edge contract-mapping engine.
- A version anti-corruption layer over a single internal backend.
- Auth-transparent: it forwards the bearer; the backend validates.
- Codec-agnostic via adapters (protobuf↔JSON shipped first).
It IS NOT:
- An auth server. Bring your own token issuer/validator (the backend's).
- A policy/authz engine. No PII redaction, no business logic.
- A message broker or database. No persistence, no replay.
- A reverse proxy / TLS terminator / router. The ingress owns that.
- A WebSocket/streaming tier. wavefront is request/response only.
The wavefront-bundle CLI is published to the alternet-dev Homebrew tap as a
prebuilt binary (no Go toolchain required):
brew install alternet-dev/tap/wavefront-bundleThe Homebrew formula installs only the CLI. The proxy server is distributed
exclusively as the multi-arch container image at
ghcr.io/alternet-dev/wavefront; see the container invocation in
Quick start below.
Generate the bundle from your OpenAPI — a file, or the live service's
/openapi.json (no manual export step) — then run the proxy against it:
# installed via Homebrew, or built from source:
# go build -o wavefront-bundle ./cmd/wavefront-bundle
# from a file…
wavefront-bundle add --openapi ./openapi.json --bundle ./bundle
# …or straight from a running service:
wavefront-bundle add --openapi https://api.internal/openapi.json --bundle ./bundleThe bundle is committed in your repo (it is the contract source of truth) and mounted or baked in for wavefront.
From source:
go build -o wavefront ./cmd/wavefront
WAVEFRONT_BUNDLE_PATH=./bundle \
WAVEFRONT_UPSTREAM_BASE_URL=http://localhost:9000 \
./wavefrontOr via the prebuilt multi-arch container:
docker run --rm -p 8080:8080 \
-e WAVEFRONT_BUNDLE_PATH=/etc/wavefront/bundle \
-e WAVEFRONT_UPSTREAM_BASE_URL=http://core-api:9000 \
-v $(pwd)/bundle:/etc/wavefront/bundle:ro \
ghcr.io/alternet-dev/wavefront:latestA bundle is a directory of immutable per-version layers plus an
operator-owned resolution.yaml. Each layer is one contract version, holding
that version's frozen descriptors:
bundle/
resolution.yaml # operator-owned: per-version resolution overrides
2024-11/ # one immutable layer per contract version
descriptors.binpb # proto FileDescriptorSet
openapi.json # the frozen internal OpenAPI surface
versions.yaml # the route binding for this version
add emits a new layer and never rewrites an existing one; remove and
retire take a version out of service, and verify gates the bundle's
consistency in your CI.
All configuration is environment variables, read once at startup.
| Var | Default | Meaning |
|---|---|---|
WAVEFRONT_BUNDLE_PATH |
— (required) | path to the descriptor bundle dir |
WAVEFRONT_UPSTREAM_BASE_URL |
— (required) | the default internal backend base URL |
WAVEFRONT_TARGETS |
(none) | named backend targets, comma-separated name=url pairs |
WAVEFRONT_LISTEN_ADDR |
0.0.0.0:8080 |
bind address for HTTP |
WAVEFRONT_METRICS_ADDR |
0.0.0.0:9090 |
bind address for /metrics |
WAVEFRONT_CONTRACT_VERSION_HEADER |
X-Api-Contract-Version |
header carrying the client's contract version |
WAVEFRONT_REQUEST_TIMEOUT_MS |
15000 |
upstream request timeout |
WAVEFRONT_READ_HEADER_TIMEOUT_MS |
10000 |
data-plane ReadHeaderTimeout (slow-headers cap) |
WAVEFRONT_READ_TIMEOUT_MS |
30000 |
data-plane ReadTimeout (headers + body) |
WAVEFRONT_MAX_BODY_BYTES |
1048576 |
inbound body cap |
WAVEFRONT_LOG_LEVEL |
info |
structured log level |
Operational notes:
- Fail-fast. The bundle is loaded once at boot; a missing or invalid bundle → refuse to start. Roll a new bundle by deploying a new process.
GET /metricsexposes Prometheus counters —wavefront_requests_totalandwavefront_errors_total, both labelled by negotiatedcontract_version; errors carrycodeplus atransform_outcomedimension that splitstransform_failedby chain side./healthand/readyare the probes.- Codec.
protobuf ↔ JSONis the only adapter today; the adapter boundary is built so another codec could be added. All non-hop-by-hop client headers are forwarded untouched.
The bundle schema and transform vocabulary are detailed in docs/protocol.md; integrating a consumer is covered in docs/embedding.md.
- docs/concepts.md — first principles + vocabulary
- docs/protocol.md — bundle schema + wire contract
- docs/embedding.md — how a consumer integrates
- docs/client.md — multi-language client integration
- docs/architecture.md — implementation shape
- docs/operations.md — running it
LLM agents: start with AGENTS.md.
Dual MIT / Apache-2.0.