Releases: alternet-dev/wavefront
v0.7.4
v0.7.4 — CORS pass-through for browser-facing deployments
v0.7.4 makes wavefront usable as a browser-facing gateway. A cross-origin SPA
call carrying Authorization triggers a CORS preflight, and wavefront had no CORS
handling: the OPTIONS preflight 404'd as an unbound route, and proxied responses
dropped the upstream's Access-Control-* headers — so every cross-origin browser
call through the gateway was blocked.
-
CORS pass-through (the upstream owns CORS). wavefront does not own CORS;
the upstream is the single source of truth. A preflight — OPTIONS with Origin
and Access-Control-Request-Method — is forwarded to the default upstream and
its response relayed, bypassing routing, negotiation, and the codec, so it no
longer 404s. On a proxied response, the upstream's Access-Control-* and Vary
headers are carried through verbatim, on every response derived from an
upstream reply: success, 204/205, declared error, the aid envelope, and the
capability-ceiling 502. Origin already reaches the upstream as a forwarded
client header, so it echoes the correct allow-origin. No new configuration or
dependencies — the allowed-origins list stays in the backend (#147).Deliberate gap: a wavefront-originated response synthesized before any upstream
call (unsupported_contract_version, unknown_route, unsupported_media_type) has
no upstream CORS to mirror and carries none. Those are misconfigured-request /
development conditions, not the normal browser flow. -
feat: forward CORS preflights and relay upstream Access-Control-* (#148)
v0.7.3
v0.7.3 — container self-probe + image HEALTHCHECK
v0.7.3 restores the orchestrator readiness signal for the container image. The
runtime image is distroless — no shell, no wget/curl — so an exec-style
container healthcheck (CMD-SHELL wget …) failed before it could probe anything
and reported a permanent false unhealthy; consumers had to drop their
healthcheck entirely.
-
wavefront probe --ready— the binary probes itself. The standard
distroless pattern: the binary GETs its own ops listener's /ready in-process
and maps the outcome to the healthcheck exit-code contract — 200 → exit 0,
anything else (non-200, connection refused, timeout) → exit 1, usage error →
exit 2. The ops address resolves from the same WAVEFRONT_METRICS_ADDR the
server binds (only that var — probing never fails because serving-only
required vars are absent), and a wildcard listen host (0.0.0.0, ::) is
rewritten to loopback for the dial. No new dependencies (#145 closed). -
Built-in image HEALTHCHECK. The Dockerfile declares the exec-form
healthcheck — HEALTHCHECK --interval=10s --timeout=3s --start-period=5s
CMD ["/wavefront", "probe", "--ready"] — so compose consumers inherit a
working readiness signal with no per-deployment configuration. Kubernetes
users keep pointing an httpGet probe at the ops port (no exec needed). A new
image-health CI job builds the image, starts it against a generated bundle,
and asserts container health flips to healthy. -
feat: add a binary self-probe and image HEALTHCHECK (#146)
v0.7.2
v0.7.2 — typed non-200 successes
v0.7.2 fixes a silent typing gap in the generator: a route whose success status
is not literally 200 lost its declared response type. The fix is additive — a
bundle whose successes are all 200 is byte-identical; only silently-untyped
non-200 routes gain their declared schema. The release also ships a
clean-auditing Homebrew formula.
-
The success response binds from the declared 2xx. wavefront-bundle add
picked the success schema from the literal "200" response key only, while the
per-status error collection deliberately skips all 2xx — so a route whose
success is 201/202 (FastAPI emits the schema under the route's status_code,
e.g. 202 for an async endpoint) fell through both collectors and silently
bound the synthetic Empty, dropping its type. The success now binds from the
declared 2xx; if a hand-authored spec declares several 2xx for one operation,
the numerically-lowest wins, deterministically. A declared 2xx with no content
at all (a 204, or a bare description-only entry) is genuinely bodyless and
still binds Empty; a 2xx declaring only non-JSON content is still rejected
(#143 closed). -
Clean-auditing Homebrew formula. The formula rendered at release time now
passes brew test-bot's tap-syntax audit: the redundant version line is
dropped (inferable from the URL), the dual license is declared as
any_of: ["MIT", "Apache-2.0"], and macOS-on-Intel resolves a URL for
brew readall while depends_on arch: :arm64 keeps the formula
Apple-Silicon-only on macOS (#142). -
fix: bind the success response_message from the declared 2xx (#144)
-
release: emit a clean-auditing Homebrew formula (#142)
v0.7.1
v0.7.1 — generator coverage for real FastAPI surfaces
v0.7.1 widens the generator's coverage of real FastAPI/OpenAPI surfaces and
brings the CI and release workflows off the deprecated GitHub Actions Node 20
runtime. The generator changes are additive — they only accept shapes that
v0.7.0 hard-rejected — so any bundle that builds today is byte-identical.
-
Non-object components lower inline. A component that is an enum, typed
dict, union, or open object — referenced by a field, the way FastAPI emits a
str, Enum(e.g. JobPolicyMode) — now lowers the same as the inline form
(scalar, map, google.protobuf.Value, google.protobuf.Struct) instead of being
rejected with "must be an object with properties". A scalar enum lowers to its
underlying scalar (string/int32), never a proto3 enum: protojson rejects
unknown enum names, so typing the enum would let an old contract reject a value
a newer backend added — the version skew wavefront exists to remove. A
non-object component bound directly as a body stays a hard error (#133). -
Bare {"type":"object"} lowers to Struct. A bare object with no properties
and no additionalProperties — the shape FastAPI emits for ValidationError.ctx
— lowers to google.protobuf.Struct like any open object, singular and as an
array item (#134). -
Illegal proto-identifier names are sanitized. A field key or component
name with a character outside [A-Za-z0-9_] (a dotted Pydantic alias, or
Pydantic v2's -Input/-Output schema split) no longer aborts descriptor
validation: the proto identifier is sanitized while the original wire key is
preserved via json_name (no wire-shape change), and collisions are
disambiguated so two distinct names never fuse (#135). -
CI and release workflows on Node 24. Every actions/* and docker/* action
in the CI and release workflows is bumped off the deprecated Node 20 runtime
to its current Node 24 major, and the release workflow's force-node24 stopgap
is removed (#136). -
feat: sanitize illegal proto-identifier names in the generator (#138)
-
feat: lower bare {"type":"object"} to google.protobuf.Struct (#139)
-
feat: lower non-object components inline like the equivalent inline schema (#140)
-
ci: migrate GitHub Actions off the deprecated Node 20 runtime (#141)
v0.7.0
v0.7.0 — generator coverage + richer error typing
v0.7.0 widens what wavefront expresses on both ends of the contract. The
generator bundles a broader range of real FastAPI/OpenAPI surfaces — empty
objects, typed and open dictionaries, and polymorphic unions — and the proxy
stops collapsing every non-success into one envelope: a declared (route, status)
is now a typed, status-preserving contract response. Generator output stays
byte-reproducible — the well-known-type descriptors are embedded in a layer only
when a schema actually uses them.
-
Empty object schemas bundle as zero-field messages. A component that is an
explicitly-empty object ({"type":"object","properties":{}}) — the shape
FastAPI emits for a fieldless model — bundles as a zero-field proto message
instead of being rejected. A bare {"type":"object"} with no properties key is
still a hard error, the open/untyped-object boundary (#118 closed). -
Typed additionalProperties lowers to a proto3 map. A typed dictionary
(additionalProperties: <scalar|$ref>, no declared properties of its own)
lowers to map<string, V> (#119 closed). -
Open objects and untagged unions lower to well-known types. An open object
(additionalProperties: true | {}) lowers to google.protobuf.Struct; an untagged
anyOf/oneOf union or an unconstrained {} schema lowers to google.protobuf.Value
(repeated as array items). google/protobuf/struct.proto is embedded in the
layer only when used, so bundles that need neither stay byte-identical. This
types FastAPI's HTTPValidationError.loc: list[str | int] (#121). -
Discriminated unions lower to Struct. A tagged anyOf/oneOf (one carrying a
discriminator) serializes flattened — the discriminator is a sibling property
and the member's fields sit at the top level — which a proto3 oneof cannot
carry (protojson encodes a oneof wrapped under the member key). It lowers to
google.protobuf.Struct, which relays the object verbatim (#129 closed). -
Upstream-origin status codes and typed error bodies. wavefront no longer
collapses every non-success to a fixed wavefront.v0.Error envelope. A declared
(route, status) is a first-class typed contract response: the upstream status
is preserved and no X-Wavefront-Error header is set. Undeclared statuses fall
to the graceful aid envelope. wavefront honors whatever status the upstream
sends except a hard capability ceiling — 206/207/208/226 and any 3xx map to
502. A per-route strict: true flips the undeclared-status default to a hard
502. resolution.yaml field transforms (rename/default/optionalize/coerce) fire
per (status, message) pair, not only on 2xx (#53 closed). -
fix: accept empty object schemas as zero-field messages (#122)
-
feat: lower a typed-dict additionalProperties to map<string,V> (#123)
-
feat: bind per-status error_messages and strict in the bundle loader (#124)
-
feat: collect and bind declared error response types in the generator (#125)
-
feat: honor upstream status, type declared errors, relay the rest (#126)
-
feat: apply field transforms per declared error status (#127)
-
test: exercise per-status error transforms across a version chain (#128)
-
feat: lower untagged unions and open objects to well-known types (#130)
-
feat: lower discriminated unions to google.protobuf.Struct (#131)
v0.6.1
v0.6.1 — OpenAPI 3.1 surface tolerance
wavefront-bundle add now accepts three OpenAPI 3.1 shapes that v0.6.0 rejected,
without loosening the fail-loud contract for genuinely unsupported constructs.
These are the shapes a real FastAPI surface emits: in the reference consumer's
schema they account for nearly every anyOf, additionalProperties, and empty
inline-schema occurrence. Generator-only — the proxy data plane is unchanged.
-
Nullable anyOf lowers to proto3 optional. The OpenAPI 3.1 nullable idiom
anyOf: [T, {type: null}] — where T is a scalar or a $ref — now lowers to a
proto3 optional field, the same result as 3.0's nullable: true. A genuine
non-nullable union (anyOf of two distinct types) is still a hard error
directing you to a $ref with a discriminator field (#112 closed). -
additionalProperties: false is a no-op. An explicit
additionalProperties: false matches proto3's closed-message default and is now
accepted as a no-op, producing byte-identical descriptors to omitting it. The
unsupported forms — additionalProperties: true and the typed-dict
additionalProperties: {schema} — remain hard errors (#113 closed). -
Empty inline schema is bodyless. An empty inline schema: {} carries no
shape, so it is treated as semantically bodyless and binds the synthetic
wavefront.gen.v.Empty message (the path introduced in v0.6.0) instead
of being rejected as an unsupported inline schema. A non-empty inline schema is
still a hard error (#114 closed). -
fix: lower the OpenAPI 3.1 nullable anyOf shape to proto3 optional (#117)
-
fix: tolerate additionalProperties: false as a no-op (#116)
-
fix: bind synthetic Empty for an empty inline schema {} (#115)
v0.6.0
v0.6.0 — real-world surface readiness
The generator and proxy now cover the read side of a real HTTP surface:
bodyless operations — GET reads, path-only POSTs, and 204 responses — bundle
and proxy end-to-end, where v0.5 rejected them. wavefront also becomes visible
in distributed traces, emitting its own attributed span per request, and the
release workflow now renders these notes from the tag annotation itself. The
span ships without the OpenTelemetry SDK — a hand-rolled OTLP/HTTP exporter
keeps the server's dependency tree free of opentelemetry and grpc (#92 resolved
by approach).
-
Bodyless operations end-to-end. wavefront-bundle add no longer rejects
operations with no request body or no 200 response: each layer synthesizes a
single wavefront.gen.v.Empty message, and bodyless operations bind it
for the request and/or response side. At runtime a request_message of Empty
produces a clean bodyless upstream call (no body, no Content-Type — not {}
with application/json), and a response_message of Empty emits no response
body. Path and query parameters still pass through verbatim (#105 closed). -
wavefront-attributed OpenTelemetry span. With
WAVEFRONT_TRACES_OTLP_ENDPOINT set, wavefront emits one wavefront.proxy span
per request to an OTLP/HTTP collector, continuing the inbound W3C traceparent
(trace id preserved, the inbound span becomes the parent, fresh child span id)
while still forwarding the client's traceparent/tracestate untouched. Each
span carries contract_version, target, and transform_outcome attributes and a
service.name resource (WAVEFRONT_TRACES_SERVICE_NAME, default wavefront);
WAVEFRONT_TRACES_SAMPLE_RATIO in (0,1] sets the root-trace sample probability.
Emission is non-blocking and best-effort — a full queue or a collector error
drops the span, never the request. Unset endpoint = disabled, zero overhead.
Implemented as a hand-rolled OTLP/JSON exporter, no OTel SDK (#71, #92
closed). -
Release notes from the tag annotation. The release workflow extracts the
annotated-tag body explicitly (git tag -l --format='%(contents)' into
--notes-file) instead of --notes-from-tag, which had silently fallen back to
the commit message on v0.5.0 (#106 closed). -
feat: synthesize a synthetic Empty message for bodyless operations (#107)
-
feat: send bodyless upstream requests / empty responses for Empty (#111)
-
feat: emit a wavefront-attributed OTLP span per request (#110)
-
fix: extract release notes from the tag annotation explicitly (#109)
v0.5.0
v0.5.0 — consumer surface
The wire matches what the docs claim, the generator can emit multi-route
bundles, the proxy serves them end-to-end, and brew install delivers
wavefront-bundle as a fast binary download. The TypeScript-specific codegen
was considered, scoped to message emission, then dropped — replaced by
docs/client.md showing how to wire any language's protoc-gen-X against the
bundle directly. OTel SDK adoption was deferred after a dependency-footprint
analysis (#92).
-
Wire-contract conformance. Five new X-Wavefront-Error codes:
internal_error, unknown_route, upstream_status (bounded passthrough set
401/403/404/405/409/410/422/429/451), unsupported_media_type, unavailable.
2xx fidelity preserved; upstream redirects refused; panic recovery; strict
(path, method) route gating. New wavefront_stdlib_boundary_total metric
labelled by status (#39, #54 closed). -
Multi-route bundle generator. wavefront-bundle add now walks every
paths.. operation in the input OpenAPI; the proxy serves multi-route
bundles via (path, method, version) negotiation. --force flag for
same-info.version re-add (#77 closed). -
Per-language client integration guide. docs/client.md documents how
to wire the bundle into TypeScript / Go / Python / Rust applications using
each language's standard protoc-gen-X. No wavefront-specific codegen
surface (#72 closed). -
Homebrew binary distribution. brew install alternet-dev/tap/wavefront-bundle
becomes a fast binary download instead of a source build; per-arch matrix
for darwin-arm64 / linux-arm64 / linux-amd64; formula scoped to the CLI
only (server stays container-only). Intel-Mac (x86_64-apple-darwin) is
intentionally not built — the macos-13 runner queue is too slow to gate a
release on; Intel-Mac users use the Linux binary via Rosetta or build from
source (#73 closed). -
Server timeout + 100-continue posture. WAVEFRONT_READ_HEADER_TIMEOUT_MS
and WAVEFRONT_READ_TIMEOUT_MS env-configurable; oversized declared bodies
rejected with 413 before any 100 Continue is sent. -
server: panic-recovery middleware and shared wire-error helper (#75)
-
feat: unknown_route 404 envelope on unmatched routes (#76)
-
feat: wavefront-bundle gen-ts-client subcommand skeleton (#78) [reverted in #100]
-
feat: per-arch binary build matrix for wavefront-bundle releases (#79)
-
feat: preserve upstream 2xx status on response (#80)
-
feat: TS message-class emission via protoc-gen-es (#81) [reverted in #100]
-
build: render-homebrew-formula.sh for wavefront-bundle releases (#82)
-
feat: upstream-status passthrough for bounded 4xx set (#83)
-
feat: emit unsupported_media_type 415 for wrong Content-Type (#84)
-
feat: refuse upstream redirects (3xx -> 502) (#85)
-
feat: env-configurable data-plane read timeouts; 413 before 100 Continue (#86)
-
feat: emit 503 unavailable on proxy path when bundle not loaded (#87)
-
feat: defense-in-depth check for proto-package collisions across bundle layers (#88)
-
test: set Content-Type on redirect tests so 415 gate doesn't intercept (#90)
-
feat: multi-route bundle generator emission core (#93)
-
build: wire render-homebrew-formula.sh into update-tap, rename formula to wavefront-bundle (#94)
-
feat: observability for stdlib-boundary HTTP responses (labelled by status) (#96)
-
docs: protocol.md and concepts.md updates for wire-contract conformance (#97)
-
feat: serve multi-route bundles end-to-end (#98)
-
feat: --force flag for wavefront-bundle add (#99)
-
docs: client.md per-language integration guide (drop gen-ts-client subcommand) (#100)
-
docs: AGENTS.md — correct stale 'no in-place reload' claim (#103)
-
build: drop Intel-Mac (x86_64-apple-darwin) from release matrix (#104)
-
OTel SDK adoption — dropped after dep-footprint analysis (#92, plus upstream open-telemetry/opentelemetry-go#8385 and #8386)
-
Per-language client codegen revisit — captured in #102
-
Intercept stdlib-boundary statuses with the wire envelope — captured in #91
-
Bundlegen polish (confidence knobs + intent files) — captured in #74
-
Inbound HTTP/2 / h2c / HTTP/3 — captured in #101
v0.4.0
v0.4.0 — drift detection, observability, and hot reload
bundle draft-shim auto-drafts a transform-shim override from two layers'
OpenAPI documents: confident stanzas for unambiguous changes, candidates
blocks for ambiguous renames the operator must resolve before bundle
verify accepts the result. Per-request structured logging and labelled
metrics (contract_version, transform_outcome) let an operator slice
traffic and failures by cohort. SIGHUP swaps the bundle in place;
buf breaking gates wavefront.v0.Error against breaking edits.
v0.3.0
v0.3.0 — multi-version bundle
One committed bundle carries every supported contract version as an immutable
per-version layer. Adds per-version resolution (a backend route, or a
transform shim), transform-shim chains across versions, multi-target routing,
and the wavefront-bundle add / remove / retire / verify tooling.