Release

Announcing Toise 0.9.0 — strict spec alignment and read-surface security

2026-07-01

Toise still starts the same way: one binary, no config — point your OTLP producers at it and the graph appears. 0.9.0 does not touch that. It sharpens two things instead: the fidelity of what Toise records from the OpenTelemetry entity-events wire, and the control you have over who authenticates on which surface. Everything is additive.

This is a small, precise release. Its point is that the graph an LLM reads should be a faithful mirror of what your producers actually reported — no quietly dropped structure, no lost intent — and that exposing the read surfaces should not force a posture onto ingest. It is not a wire-contract break and needs no data migration; a 0.8 deployment upgrades in place.

Faithful to the entity-events spec

The OpenTelemetry spec types entity.description as an AnyValue — scalars, arrays, or nested maps. Earlier Toise kept only the four scalar kinds and dropped the rest. 0.9.0 ingests the full AnyValue end to end (ingest, store, projection, GraphQL and MCP): an array or a nested map survives intact and renders on read as compact JSON tagged array / kvlist. Identity stays scalar by contract — exact-match identity is over scalar strings, so entity.id is still a flat map and a nested value there is surfaced, never hashed.

Two more alignments round it out. A delete can now carry entity.delete.reason — an open enum (terminated, expired, evicted, …, never checked against a closed set) — captured, persisted, and surfaced on recent_changes, graph_diff, and the GraphQL ChangeEvent.deleteReason, so a consumer can tell a graceful shutdown from an eviction. And entity.report.interval == 0 (or absent) now means "no cadence": such an entity is removed only by an explicit entity.delete, never expired by the liveness sweep — a distinction now locked by a conformance test.

Decoupled ingest and read authentication

Until now, configuring any token armed both surfaces: protecting GraphQL and MCP with tokens also made OTLP ingest demand a bearer. But when producers already authenticate by mutual TLS, that bearer is redundant. The new opt-in ingest_mtls_only (TOISE_INGEST_MTLS_ONLY, requires a client-CA) separates the two: ingest is authenticated by the verified client certificate alone, while the read surfaces keep requiring their per-client scoped tokens (role read / full, individually revocable) or OIDC. Off by default; the bearer-on-ingest posture is unchanged.

Also — a reconciliation fix and a security bump

Out-of-order edges are parked until their endpoint arrives. If the endpoint reappeared on a slower cadence than the buffer window — a host re-announced every few minutes while the buffer expired in seconds — the edge could be dropped just before its next heartbeat, leaving a service momentarily detached from its host. 0.9.0 holds a parked edge for at least one of the source's re-emit cycles, so the endpoint's next heartbeat attaches it; the hold auto-scales from the reported interval. Separately, the Go toolchain is pinned to 1.26.4, clearing three reachable standard-library advisories.


Get it

go install github.com/toise-dev/toise/cmd/toise-server@v0.9.0

Binaries for linux and darwin (amd64 / arm64) are on the release, each with a checksum. See the 0.9.0 docs, the 0.8 → 0.9 migration guide, and the changelog. Producers using the SDK can pick up the new fidelity with pkg/emit/v0.3.0.