The release that makes the graph legible. 0.17.0 set out to say what an
answer is worth; 0.18.0 makes the answer readable — to the consumer that reads it,
which is an MCP client or a GraphQL caller. An entity now carries a name meant to
be shown, and the compact scan mode carries it too. Both changes come from consumer
feedback on answers that were correct but hard to read at a glance.
No wire-contract break, no data migration — a 0.17 deployment upgrades in place.
Read this first if you consume entity labels
A label now contains a value that drifts. A rename changes it, where before it
did not. Anything that groups, joins or matches on a label must move to
identity_fingerprint, which is returned beside it, is identical across replicas,
survives re-minting, and does not move when a thing is renamed. Code that grouped
by label across incarnations should move to the fingerprint now.
Highlights
A readable name of its own
Entities carry display_name (MCP, omitted when empty) and displayName
(GraphQL) beside identityFingerprint. Render it as-is: it is the value meant to
be shown on its own. Where no single attribute names a thing, it composes one — a
network endpoint renders 10.0.0.5:5432, IPv6 bracketed.
Why it was needed: in verbosity: compact — the mode that exists to scan many
entities cheaply — 21 hosts rendered as host host.id=<uuid> and 94 containers as
64 hex characters, while host.name was present on 21/21 and container.name on
94/94. The names were in the data; the compact mode did not show them.
Both spellings of the interface name
senhub-agent 0.6.0 moves its identifying key from interface.name to
network.interface.name (the semantic-convention spelling, release candidate since
semconv v1.44.0). The two are different identities, so an interface is re-minted
once when its producer migrates — expected, and it happens once. Toise accepts both
as telemetry join keys and as display names for as long as the retention window
holds pre-migration observations. A test pins that the two remain distinct: merging
them would be the silent merge exact identity exists to forbid.
One producer could retract another producer's edge
Entity liveness has been per-producer reference-counted since ADR 0019. Edges were
not: one reference per edge, and any removal deleted it outright.
Several producers emit the same network.address because they reference it as a
gateway; only the one owning the interface carries the bound_to descriptor. Every
other emission arrived with no descriptor, the reconciler read the absence as a
retraction, and the edge went — then came back, then went. Measured: about 156
assert/retract cycles a day on the single edge joining two machines, each removal
stamped delete_source=producer although that producer had never asserted the edge.
Edges are now reference-counted per producer. An edge survives while any producer
asserts it and is removed when the last reference goes. Behaviour change: an
edge asserted by two producers now survives one of them dropping it. Endpoint death
is unchanged — a deleted entity still cascades every incident edge. Liveness
snapshots from earlier builds restore as the anonymous producer, so an edge keeps
its backstop across the upgrade with no migration step.
In the example viewer
examples/graph-viz/ is an example, not a product surface — the human-facing
picture lives at the edge of this project on purpose. Two changes there, for
anyone who uses it or starts from it.
It now folds children into their owner by default. Measured on a real lab
graph before changing anything, 478 of 546 entities were the child of a single
owner, so folding the ownership chain draws 48 nodes instead of 546; on a
367-node deployment it draws 3. A folded owner carries a badge naming what it
holds, the counter states the folded total, and double-click opens it.
And derived links are now drawn by default rather than hidden. That one is
worth a sentence beyond the example, because the reason is core: the engine stores
facts only and never merges by heuristic (ADR 0022), so an IP carried as a plain
attribute is not linked to the network.address entity for that IP. Toise
resolves that join at read time and never writes it back (ADR 0032) — which makes
the resolved view often the only thing joining two halves of a real chain. An
operator had reported seeing no link between a gateway and a dashboard it talks
to; the link was there, hidden by a default.
Known limitation, not fixed here
On a tenant ingesting thousands of events per minute, the change-feed read
(recentChanges, entityHistory bounded by time) under-reports and can answer 0
for a period that was busy, without saying so —
#397. Pre-existing, present in
0.17.1, not made worse here. Below a few hundred events per minute it is accurate.
The event log itself is complete and reading the graph at a past instant is exact.
Announcement: https://toise.dev/blog/v0.18.0/ · Docs: https://toise.dev/docs/0.18.0/
Full details in the changelog.