<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Avuru Obs Blog</title>
        <link>https://avuruobs.io/changelog</link>
        <description>Avuru Obs Blog</description>
        <lastBuildDate>Sun, 16 Aug 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Hand out access from the app — and give your scripts a key of their own]]></title>
            <link>https://avuruobs.io/changelog/oidc-mapping-api-tokens</link>
            <guid>https://avuruobs.io/changelog/oidc-mapping-api-tokens</guid>
            <pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Settings → Access now edits the SSO group→role mapping, with the chart's rules still authoritative, and mints personal API tokens that follow their owner's live permissions.]]></description>
            <content:encoded><![CDATA[<p>Two gaps closed in Settings → Access: the identity-provider group mapping is no
longer a value only a <code>helm upgrade</code> could change, and scripts get a credential
of their own instead of a borrowed session cookie.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="map-sso-groups-to-roles-from-the-app">Map SSO groups to roles, from the app<a href="https://avuruobs.io/changelog/oidc-mapping-api-tokens#map-sso-groups-to-roles-from-the-app" class="hash-link" aria-label="Direct link to Map SSO groups to roles, from the app" title="Direct link to Map SSO groups to roles, from the app" translate="no">​</a></h2>
<p>Which SSO group grants which role on which projects was a chart value —
<code>auth.oidc.mapping</code> in <code>values.yaml</code>, one <code>helm upgrade</code> per change — so giving
a team access meant a deploy. Settings → Access now shows those rules and lets
an admin add, edit and delete rules of their own beside them.</p>
<ul>
<li class=""><strong>The chart stays the declared base.</strong> Its rules render read-only, and on a
name collision the chart wins: an authored rule for a group the chart also
declares is kept and marked as overridden, with the reason on its row,
instead of being silently ignored or refused outright.</li>
<li class=""><strong>A change applies to that group's next sign-in or token refresh</strong>, and
reaches every hub replica within about fifteen seconds. That bound is stated
in the panel, so a stale read on another replica right after a save is not
mistaken for a failed one.</li>
<li class=""><strong>Reset</strong> deletes every rule authored in the app and returns the install to
exactly what the chart declares. An install with no identity provider
configured never sees the panel — there is nothing for it to map.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="personal-api-tokens">Personal API tokens<a href="https://avuruobs.io/changelog/oidc-mapping-api-tokens#personal-api-tokens" class="hash-link" aria-label="Direct link to Personal API tokens" title="Direct link to Personal API tokens" translate="no">​</a></h2>
<p>The hub API is meant to outgrow the browser — a Grafana data source and a CLI
are on the <a class="" href="https://avuruobs.io/docs/roadmap">roadmap</a> — but the only credential was a session
cookie. Settings → Access now mints <strong>personal API tokens</strong>: create one, with
an optional expiry, copy it once, and send it as
<code>Authorization: Bearer avurut_…</code>.</p>
<ul>
<li class=""><strong>Shown once, hashed forever.</strong> Only the SHA-256 of a token is stored; the
raw value appears exactly once, at creation. The <code>avurut_</code> prefix says which
<em>kind</em> of credential leaked if one ever shows up in a log.</li>
<li class=""><strong>A token is its owner, live.</strong> It carries no permissions of its own — every
request resolves to the owner's current roles and project grants, so
disabling a user disables every token they hold, structurally rather than by
cleanup.</li>
<li class=""><strong>No silent downgrade.</strong> A bad or expired bearer token is a clean <code>401</code>,
never a fall-through to the anonymous viewer an install may have enabled.</li>
<li class=""><strong>Revocation is immediate</strong> and self-service; a global admin can list and
revoke anyone's. The list shows when each token was last used, so a stale
credential is visible before it becomes a problem.</li>
</ul>]]></content:encoded>
            <category>UI</category>
            <category>Security</category>
        </item>
        <item>
            <title><![CDATA[v0.5.0: operate it from the UI]]></title>
            <link>https://avuruobs.io/changelog/v0-5-0</link>
            <guid>https://avuruobs.io/changelog/v0-5-0</guid>
            <pubDate>Sun, 16 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Collection, service groups, the SSO role mapping and API credentials are now administered from the app; the Dashboard answers 'how is the estate doing?' on one screen; and the service map says what is wrong, not just that something is.]]></description>
            <content:encoded><![CDATA[<p><strong>v0.5.0</strong> closes the gap between <em>running</em> avuru obs and <em>changing</em> it. Nearly
everything an operator used to reach through <code>values.yaml</code> and a redeploy —
collection, service groups, the SSO role mapping, API credentials — now lives
in the app, and the app itself opens on a screen that answers <em>how is the
estate doing?</em></p>
<!-- -->
<ul>
<li class="">
<p><strong>Turn signals on and off from the UI.</strong>
<a class="" href="https://avuruobs.io/changelog/collection-runtime-control">Settings → Collection became writable</a>:
switch OBI traces, logs, infra-metrics, profiling or energy collection, edit
the excluded namespaces, and the sensor follows in seconds — no
<code>helm upgrade</code>. Off by default; opting in grants the hub a deliberately
narrow Role over its own sensor ConfigMaps and DaemonSet, nothing else, and
the write-through path is proven end to end against a real cluster in CI.</p>
</li>
<li class="">
<p><strong>Say which services matter, from the app.</strong>
<a class="" href="https://avuruobs.io/changelog/service-groups-ui">Health groups are authored in Settings → Groups</a>
— name, criticality tier, covered namespaces or services — and apply to the
next health read. Chart-declared groups stay read-only and win a name
collision; auto-grouping by namespace is unchanged, so nothing disappears
while you organize.</p>
</li>
<li class="">
<p><strong>One screen for how the estate is doing.</strong> The
<a class="" href="https://avuruobs.io/changelog/dashboard-overview">Dashboard</a> is the landing route:
service-group health, live topology beside the firing alerts, and Kubernetes
capacity in three bands. Each band follows its module — a band whose module
is off simply does not mount — and with service health off the summary falls
back to the busiest services rather than inventing a second status model.</p>
</li>
<li class="">
<p><strong>The service map says what is wrong, not just that something is.</strong> Node
rings now show the service's actual status
<a class="" href="https://avuruobs.io/changelog/service-map-restyle">read from the same rollup the Service Health board uses</a>;
edges carry the caller-side p50/p95 that call path actually cost; hovering a
node focuses its neighbourhood; and search, a problems-only toggle and a
group filter live in the URL, so a narrowed map is a link. The carbon lens
moved to a halo so health and gCO2e are visible at once.</p>
</li>
<li class="">
<p><strong>Grant access from the app.</strong> The
<a class="" href="https://avuruobs.io/changelog/oidc-mapping-api-tokens">OIDC group→role mapping is now editable in Settings → Access</a>
— the chart's rules stay authoritative and an overridden rule says why — and
the same tab mints <strong>personal API tokens</strong>: hashed at rest, shown once,
resolving to their owner's live permissions on every request, so disabling a
user disables every token they hold.</p>
</li>
<li class="">
<p><strong>Where the data lives, and who can touch it.</strong>
<a class="" href="https://avuruobs.io/changelog/settings-storage-access">Settings → Storage and Settings → Access</a>:
the ClickHouse connection read-only with per-signal usage and retention shown
against the TTL the tables actually enforce, and a permissions matrix the hub
derives from its own route guards, so it cannot drift from what is enforced.</p>
</li>
<li class="">
<p><strong>Find a pod on the Nodes screen.</strong> Both tables
<a class="" href="https://avuruobs.io/changelog/nodes-sort-filter">sort by any column and filter</a> by name,
namespace or workload; filters live in the URL and counts read "N of M", so
a narrowed table cannot be misread as a shrinking cluster.</p>
</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="security">Security<a href="https://avuruobs.io/changelog/v0-5-0#security" class="hash-link" aria-label="Direct link to Security" title="Direct link to Security" translate="no">​</a></h3>
<ul>
<li class=""><strong>Three green endpoints answered without authentication.</strong>
<code>GET /api/v1/green/summary</code>, <code>/green/budgets</code> and <code>/green/report</code> were
registered without the session middleware, so an install with authentication
<em>on</em> served any project's energy and carbon figures to an unauthenticated
caller. All three now require the viewer role and honour project grants, and
a test asserts over the whole project-data route set. Details in the
<a class="" href="https://avuruobs.io/changelog/green-endpoints-auth">security note</a>.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="fixed">Fixed<a href="https://avuruobs.io/changelog/v0-5-0#fixed" class="hash-link" aria-label="Direct link to Fixed" title="Direct link to Fixed" translate="no">​</a></h3>
<ul>
<li class=""><strong>The shared demo account was offered a password form it could never
submit.</strong> The hub was right to refuse the change — the demo credential is
shared and re-keyed on every boot — but the UI was never told.
<code>/api/v1/auth/me</code> now states whether self-service rotation applies and why
not, and the Account tab renders the explanation instead of the form.</li>
</ul>
<p><strong>Upgrading from v0.4.x</strong> is a normal <code>helm upgrade</code>. Schema migrations
<code>0016</code>–<code>0018</code> apply automatically (<code>hub.autoMigrate</code>). See the
<a class="" href="https://avuruobs.io/docs/releases">Releases</a> page and the
<a href="https://github.com/avuruvision/avuru-obs/releases/tag/v0.5.0" target="_blank" rel="noopener noreferrer" class="">GitHub release</a>.</p>]]></content:encoded>
            <category>Platform</category>
            <category>UI</category>
            <category>Security</category>
        </item>
        <item>
            <title><![CDATA[See what's actually wrong on the service map, at a glance]]></title>
            <link>https://avuruobs.io/changelog/service-map-restyle</link>
            <guid>https://avuruobs.io/changelog/service-map-restyle</guid>
            <pubDate>Tue, 11 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Node rings now read the same health rollup as the Service Health screen, edges carry the caller's own latency, hovering focuses the graph, and every filter is a link you can share.]]></description>
            <content:encoded><![CDATA[<p>A node used to turn red the moment <em>any</em> error happened in the window — never
telling you whether the service was actually in trouble. The service map now
answers that question, and a few more.</p>
<!-- -->
<ul>
<li class="">
<p><strong>Status rings, not error dots.</strong> A node's ring is its real health —
healthy, degraded, down or idle — read straight from the same
<a class="" href="https://avuruobs.io/docs/signals/service-health">dependency-aware rollup</a> the Service Health
screen uses. The map no longer re-derives thresholds of its own, so the two
screens can't disagree. A service the rollup doesn't cover reads as
<em>unknown</em>, never as healthy.</p>
</li>
<li class="">
<p><strong>Real per-edge latency.</strong> Edges now carry p50 and p95 measured from the
<strong>caller's</strong> side of the call — what that path actually cost, network and
queueing included. That's deliberately different from the p95 a node shows
for its own server-side work, and the gap between the two is often the
point: in the demo data the node reads <code>p95 200ms</code> while the edge into it
reads <code>p95 220ms</code> — the caller paid 20ms the callee never saw. Edges derived
from network flows, not spans, simply omit the field rather than show a
false zero.</p>
</li>
<li class="">
<p><strong>Hover to focus.</strong> Hovering a node fades everything outside its
neighbourhood, thickens its edges, and labels each one with rpm, p95, error
rate and <a class="" href="https://avuruobs.io/docs/signals/network-health">TCP RTT</a> where measured, arrowheads
showing direction without your having to trace the line.</p>
</li>
<li class="">
<p><strong>Filters and controls, all in the URL.</strong> Search by name, a "problems only"
toggle and a service-group filter — each one lands in the query string, so a
narrowed map is a link you can paste to a teammate instead of a screen you
describe. Zoom, fit-to-view, re-layout and a legend round it out. The status
and group filters only appear when service health is running, since both
read its rollup.</p>
</li>
<li class="">
<p><strong>Carbon moved to a halo.</strong> The ring was the only border a node had, so the
<a class="" href="https://avuruobs.io/docs/signals/green">carbon lens</a> now draws its gCO2e as a soft halo around
it instead — a node can show its health ring and its carbon halo at once.</p>
</li>
<li class="">
<p><strong>The Dashboard's map gets it too.</strong> The compact topology on the Dashboard
renders the same component, so it picks up rings and hover focus for free,
using health data the Dashboard was already fetching.</p>
</li>
</ul>]]></content:encoded>
            <category>Service map</category>
            <category>UI</category>
        </item>
        <item>
            <title><![CDATA[Open the app and know how your estate is doing]]></title>
            <link>https://avuruobs.io/changelog/dashboard-overview</link>
            <guid>https://avuruobs.io/changelog/dashboard-overview</guid>
            <pubDate>Sat, 08 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A Dashboard that answers the first question — service group health, topology, firing alerts and Kubernetes capacity on one screen. It is now where the product opens.]]></description>
            <content:encoded><![CDATA[<p>Until now the product opened on a screen that needed you to already have a
hypothesis. The new <strong>Dashboard</strong> gives you one, and it is where the app now
lands.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-screen-three-answers">One screen, three answers<a href="https://avuruobs.io/changelog/dashboard-overview#one-screen-three-answers" class="hash-link" aria-label="Direct link to One screen, three answers" title="Direct link to One screen, three answers" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>Are my services healthy?</strong> A card per service group — the groups <em>you</em>
authored in Settings → Groups — with its criticality tier, its status, and
the rate, latency and error rate behind that status. Worst status sorts
first, so a group in trouble is never the one pushed off the end of the row.</p>
</li>
<li class="">
<p><strong>What is talking to what, and is anything firing?</strong> The service map at
overview scale, side by side with everything currently alerting. Not a
screenshot of the map: the same graph, so a service that goes red here is red
there.</p>
</li>
<li class="">
<p><strong>Is the cluster under strain?</strong> Nodes, pods, namespaces and workloads at a
glance, plus memory utilisation per node, busiest first.</p>
</li>
</ul>
<p>Every card is a link into the screen that explains it. The Dashboard is where
you notice; the existing screens are where you investigate.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="it-works-on-the-install-you-actually-run">It works on the install you actually run<a href="https://avuruobs.io/changelog/dashboard-overview#it-works-on-the-install-you-actually-run" class="hash-link" aria-label="Direct link to It works on the install you actually run" title="Direct link to It works on the install you actually run" translate="no">​</a></h2>
<p>Each band belongs to a module, and a band whose module is off simply isn't
there — the screen never shows you a panel it can't fill, and never calls an
API your install doesn't run. Turn off service health and the first band falls
back to your busiest services rather than disappearing. Run nothing but the
core and the Dashboard still opens, with the map and your services on it.</p>
<p>Those fallback cards deliberately carry <strong>no</strong> health status. Thresholds and
critical-dependency propagation belong to the service-health module; inventing
a second set of them for this screen would put two different answers to the
same question in front of you.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-capacity-does-and-does-not-claim">What capacity does and does not claim<a href="https://avuruobs.io/changelog/dashboard-overview#what-capacity-does-and-does-not-claim" class="hash-link" aria-label="Direct link to What capacity does and does not claim" title="Direct link to What capacity does and does not claim" translate="no">​</a></h2>
<p>The capacity band reports what the sensor measures. Memory shows a real
utilisation bar, because both halves — used and available — are collected.</p>
<p>CPU is reported as <strong>cores in use</strong>, not a percentage, because no node capacity
figure exists to divide by: node metrics come from the kubelet, which does not
publish allocatable CPU. A percentage here would have had to invent its own
denominator, and a capacity reading you cannot trust is worse than an honest
absolute. Each bar carries its number as well as its colour, so nothing on this
screen has to be read by hue alone.</p>
<p>See the <a class="" href="https://avuruobs.io/docs/status">feature status</a> page for where the Dashboard sits among
the other screens.</p>]]></content:encoded>
            <category>UI</category>
            <category>Platform</category>
        </item>
        <item>
            <title><![CDATA[Turn signals on and off from the UI — no redeploy, no cluster access]]></title>
            <link>https://avuruobs.io/changelog/collection-runtime-control</link>
            <guid>https://avuruobs.io/changelog/collection-runtime-control</guid>
            <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Settings → Collection becomes writable: switch traces, logs, metrics, profiling or energy collection on or off and the sensor picks it up in seconds, behind an opt-in flag and a deliberately narrow Role.]]></description>
            <content:encoded><![CDATA[<p>Deciding what Avuru Obs collects used to mean editing <code>values.yaml</code>, running
<code>helm upgrade</code>, and holding the cluster permissions to do it — so in practice
collection stayed whatever it was on install day. Now an admin changes it from
the app, and the sensor follows within seconds.</p>
<!-- -->
<ul>
<li class="">
<p><strong>Collection is a setting, not a deployment.</strong> Settings → Collection switches
eBPF traces, logs, infrastructure metrics, profiling and energy collection on
or off, and edits the excluded-namespace list. Turn profiling off at 2 a.m.
because it is costing you more than it is telling you, and turn it back on in
the morning — without touching the chart.</p>
</li>
<li class="">
<p><strong>What the screen shows is what the sensor is doing.</strong> It reports the
<em>effective</em> configuration: your chart values with the overlay applied, not one
or the other. "Reset to defaults" clears the overlay and reconciles the
cluster back to exactly what the chart declares — a reset that only forgot
the setting would leave the two quietly disagreeing.</p>
</li>
<li class="">
<p><strong>Off by default, and narrow when you turn it on.</strong> The feature is gated on
<code>collection.runtimeControl.enabled</code>. Opting in grants the hub permission to
update its <strong>own four named sensor ConfigMaps</strong> and patch its <strong>own named
sensor DaemonSet</strong>, in its own namespace — nothing cluster-wide, nothing
belonging to anyone else. The hub rolls the sensor using its own annotation
and leaves the chart's ownership untouched, so a later <code>helm upgrade</code> behaves
exactly as before. With the flag off, nothing changes and no extra permission
is granted.</p>
</li>
<li class="">
<p><strong>Verified against a real cluster, not a mock.</strong> The Helm smoke gate now
installs with the feature on, writes a setting through the API, and asserts it
reaches the sensor's configuration and rolls the DaemonSet — then resets and
asserts the cluster comes back. A change that is saved but never reaches the
sensor is the failure that matters here, and the gate is built to catch
exactly that.</p>
</li>
</ul>
<p>Configuration lives in the chart under <code>collection.runtimeControl</code>. See
<a class="" href="https://avuruobs.io/docs/setup/kubernetes">Kubernetes setup</a> for the flag, and
<a class="" href="https://avuruobs.io/docs/setup/modules">modules</a> for how each signal is gated.</p>]]></content:encoded>
            <category>Platform</category>
            <category>UI</category>
        </item>
        <item>
            <title><![CDATA[Security: the green endpoints required no session]]></title>
            <link>https://avuruobs.io/changelog/green-endpoints-auth</link>
            <guid>https://avuruobs.io/changelog/green-endpoints-auth</guid>
            <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Three energy and carbon endpoints were registered without the session middleware, so an unauthenticated caller could read any project's figures on an install with authentication enabled. Fixed; upgrade if you run the green module.]]></description>
            <content:encoded><![CDATA[<p><code>GET /api/v1/green/summary</code>, <code>/green/budgets</code> and <code>/green/report</code> were
registered without the session middleware that every other signal route uses.
On an install with authentication <strong>enabled</strong>, all three answered <code>200</code> to a
caller with no session at all.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-was-exposed">What was exposed<a href="https://avuruobs.io/changelog/green-endpoints-auth#what-was-exposed" class="hash-link" aria-label="Direct link to What was exposed" title="Direct link to What was exposed" translate="no">​</a></h2>
<p>For any project named in the request header, without signing in:</p>
<ul>
<li class="">per-service energy in watt-hours and carbon in gCO2e,</li>
<li class="">monthly carbon budget usage,</li>
<li class="">the CSRD-ready report export.</li>
</ul>
<p>The routes are read-only, so nothing could be changed through them. But the
data is a fair map of what an estate runs and how hard it works — service
names, relative load, and the shape of a workload over time.</p>
<p>Nothing else was affected: every other signal route — traces, logs, metrics,
service map, health, alerting, error tracking, infrastructure — was already
behind the session middleware and enforcing per-project grants.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-it-happened">Why it happened<a href="https://avuruobs.io/changelog/green-endpoints-auth#why-it-happened" class="hash-link" aria-label="Direct link to Why it happened" title="Direct link to Why it happened" translate="no">​</a></h2>
<p>The routes were wired with the bare handler wrapper rather than the guarded
one, so no identity was ever attached to the request. The per-project scope
check then treats "no identity" as "authentication is switched off" — the
branch that exists so an <code>auth.enabled=false</code> install keeps working — and
allowed the request through.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="am-i-affected">Am I affected?<a href="https://avuruobs.io/changelog/green-endpoints-auth#am-i-affected" class="hash-link" aria-label="Direct link to Am I affected?" title="Direct link to Am I affected?" translate="no">​</a></h2>
<p>You are if <strong>all</strong> of these hold:</p>
<ul>
<li class="">the <a class="" href="https://avuruobs.io/docs/signals/green">green module</a> is enabled (it is <strong>off by default</strong>,
so an install that never turned it on was never exposed),</li>
<li class="">authentication is enabled, and</li>
<li class="">the hub's API is reachable by someone you would not grant a viewer role.</li>
</ul>
<p>An install where the hub is only reachable inside the cluster was not exposed
to anyone who could not already reach far more.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-fix">The fix<a href="https://avuruobs.io/changelog/green-endpoints-auth#the-fix" class="hash-link" aria-label="Direct link to The fix" title="Direct link to The fix" translate="no">​</a></h2>
<p>All three routes now require the viewer role and honour per-project grants,
exactly like every other signal. Upgrade to a build containing this change.</p>
<p>A test now asserts that <strong>every</strong> project-data route answers <code>401</code> without a
session, rather than checking them one at a time — the gap survived because
nothing had ever asserted over the whole set. It was found while indexing every
route's guard to build a permissions view for Settings, which is the same idea
applied to the product: derive what the rules are from what the server
enforces, instead of writing them down twice.</p>]]></content:encoded>
            <category>Security</category>
            <category>Platform</category>
        </item>
        <item>
            <title><![CDATA[Find the pod you are looking for: sorting and filtering on Nodes]]></title>
            <link>https://avuruobs.io/changelog/nodes-sort-filter</link>
            <guid>https://avuruobs.io/changelog/nodes-sort-filter</guid>
            <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The Nodes and Pods tables now sort by any column and filter by name, namespace or workload, with the filter kept in the URL so a narrowed view is a link you can send.]]></description>
            <content:encoded><![CDATA[<p>On a real cluster the Pods table is a hundred-plus rows in no particular order,
and the only way through it was the browser's own find-in-page. Both tables on
the Nodes screen now sort and filter.</p>
<!-- -->
<ul>
<li class="">
<p><strong>Sort by any column.</strong> Nodes by name, CPU, memory, network or pod count;
pods by name, namespace, workload, node, CPU or memory. Nodes stay in name
order by default, so the roster does not rearrange itself between refreshes,
and busiest-first is one click away. Network sorts on send and receive
together — ranking on receive alone would file a send-heavy node as quiet.</p>
</li>
<li class="">
<p><strong>Filter as you type.</strong> Nodes by name; pods by name, namespace <em>or</em> workload,
plus a namespace picker that appears once there is more than one namespace to
choose between. Nothing waits on a query: the rows are already in the browser.</p>
</li>
<li class="">
<p><strong>A filtered view is a link.</strong> The filter lives in the URL, like every other
view here, so you can send a colleague the exact table you are looking at
rather than describing it.</p>
</li>
<li class="">
<p><strong>Counts that cannot mislead.</strong> With a filter active the heading reads "N of
M", so a narrowed table is never mistaken for a shrinking cluster. And a
filter that matches nothing says so — instead of showing the "install the
sensor to collect node metrics" empty state, which would send you off to debug
a perfectly healthy install.</p>
</li>
</ul>
<p>See <a class="" href="https://avuruobs.io/docs/signals/metrics">infrastructure metrics</a> for what the Nodes screen
collects and how.</p>]]></content:encoded>
            <category>UI</category>
        </item>
        <item>
            <title><![CDATA[Say which services matter, without a redeploy]]></title>
            <link>https://avuruobs.io/changelog/service-groups-ui</link>
            <guid>https://avuruobs.io/changelog/service-groups-ui</guid>
            <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Service health groups — name, criticality tier and selector — are now created, edited and deleted in Settings → Groups, and apply to the next health read.]]></description>
            <content:encoded><![CDATA[<p>Deciding that <code>payments</code> is your most critical service used to mean editing
<code>values.yaml</code>, running <code>helm upgrade</code>, and holding the cluster permissions to do
it. So in practice it never happened: the Service Health board showed one
auto-discovered group per namespace and the tier lanes sat empty. Groups are now
authored in the app.</p>
<!-- -->
<ul>
<li class="">
<p><strong>Settings → Groups.</strong> Create a group from a name, a criticality tier (T0–T3)
and the namespaces or services it covers; edit or delete it later. The change
applies to the next health read — no restart, no redeploy, no PR. The Service
Health board links straight to the editor instead of naming a config key.</p>
</li>
<li class="">
<p><strong>Auto-grouping still works.</strong> Services no group claims keep falling into
their namespace at the default tier, exactly as before, so nothing disappears
while you organize.</p>
</li>
<li class="">
<p><strong>Groups declared in the chart keep working, and win.</strong> They render read-only,
because an install that manages its groups in Git must not have them quietly
overridden from a browser. The conflict is refused when you try to save it,
rather than discovered at the next upgrade — and if the chart later claims a
name you already authored, that group is flagged as overridden instead of
silently ceasing to group anything.</p>
</li>
<li class="">
<p><strong>Alerting and the board cannot disagree.</strong> The two sources are merged in one
place shared by the health API and the alert evaluator. That matters because
the evaluator does not read the API: merging anywhere else would have meant a
group you created showing as critical on the board while nothing ever paged on
it. A test drives both paths and then fires a real rule on a group that exists
only in the database.</p>
</li>
<li class="">
<p><strong>Safe by construction.</strong> Writes are admin-only and validated against the same
rules the chart config is checked with at startup, so nothing you can save
here will fail the hub's next boot. Everyone else can read the definitions —
seeing how the estate is organized is not a privilege.</p>
</li>
</ul>
<p>Thresholds, critical edges and the default tier stay in
<a class="" href="https://avuruobs.io/docs/signals/service-health"><code>serviceGroups</code></a> for now; groups are the part
people need daily.</p>]]></content:encoded>
            <category>UI</category>
            <category>Platform</category>
        </item>
        <item>
            <title><![CDATA[See where your data lives, and who can touch it]]></title>
            <link>https://avuruobs.io/changelog/settings-storage-access</link>
            <guid>https://avuruobs.io/changelog/settings-storage-access</guid>
            <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Two new Settings tabs: Storage (connection, per-signal usage, and retention you can trust) and Access (which role may do what, derived from the hub's own guards).]]></description>
            <content:encoded><![CDATA[<p>Two questions an operator asks in the first week — <em>where is my telemetry
actually stored?</em> and <em>what can this role do?</em> — now have a screen each.</p>
<!-- -->
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="storage">Storage<a href="https://avuruobs.io/changelog/settings-storage-access#storage" class="hash-link" aria-label="Direct link to Storage" title="Direct link to Storage" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>The connection, read-only.</strong> Address, database and user for the ClickHouse
this hub is talking to. Read-only is the honest answer rather than a missing
feature: ClickHouse is the store, so it cannot hold its own connection
string. The card says so and gives you the <code>--set</code> line, instead of a form
that would quietly fail. It is shown even while ClickHouse is unreachable,
which is when "which address did we fail to reach?" is the first question.</p>
</li>
<li class="">
<p><strong>Per-signal usage</strong>, moved here from Status: size on disk, compression
ratio, row count, how far back the data goes. Each tab now answers one
question — Status is <em>is it healthy right now</em>, Storage is <em>what is in it</em>.</p>
</li>
<li class="">
<p><strong>Retention you can trust.</strong> The days in your values are what the install is
<em>configured</em> to keep. The TTL on the tables is what ClickHouse is <em>enforcing</em>
— and changing a retention value does nothing to tables that already exist
until the migration re-applies it. Until then the configured number is a
wish. When the two disagree the column says <code>30d → 7d</code> rather than repeating
the wish, and a database whose retention has not been applied yet reads
<code>→ none</code>.</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="access">Access<a href="https://avuruobs.io/changelog/settings-storage-access#access" class="hash-link" aria-label="Direct link to Access" title="Direct link to Access" translate="no">​</a></h2>
<p>Which role may read, and which may change, each area of the product — service
map, traces, error tracking, alerting, users, and the rest.</p>
<p>Every cell comes from the hub, which derives it from the authorization its
routes are actually registered with. Nothing is transcribed into the browser:
add an admin-only endpoint anywhere and it appears here; change what a route
requires and this changes with it. A permissions table that can disagree with
what the server enforces is worse than none at all, because it gets believed.</p>
<p>An install running without authentication says so at the top of the tab,
instead of presenting a model that nothing is enforcing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-thing-this-found">One thing this found<a href="https://avuruobs.io/changelog/settings-storage-access#one-thing-this-found" class="hash-link" aria-label="Direct link to One thing this found" title="Direct link to One thing this found" translate="no">​</a></h2>
<p>Building the matrix meant indexing every route's guard — which is how we
noticed that three of them had none. See
<a class="" href="https://avuruobs.io/changelog/green-endpoints-auth">the security note</a>: if you run the green
module with authentication enabled, it is worth two minutes of your time.</p>]]></content:encoded>
            <category>UI</category>
            <category>Platform</category>
            <category>Security</category>
        </item>
        <item>
            <title><![CDATA[v0.4.0: manage users end to end — and three ways into an account, closed]]></title>
            <link>https://avuruobs.io/changelog/v0-4-0</link>
            <guid>https://avuruobs.io/changelog/v0-4-0</guid>
            <pubDate>Fri, 07 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Settings gains full user management and self-service password change, three authentication bypasses are fixed, and an install whose schema migration never ran now repairs itself instead of failing every query forever.]]></description>
            <content:encoded><![CDATA[<p><strong>v0.4.0</strong> finishes the account lifecycle you started in v0.2: users can now be
edited, reset and deleted from the UI, and anyone can change their own password.
Reviewing that surface turned up three ways to get into an account that
shouldn't have worked — all three are closed. Alongside them, an install that
silently lost its schema migration now heals itself.</p>
<!-- -->
<ul>
<li class="">
<p><strong>Users, managed end to end.</strong> Settings → Users now edits a user's name and
role grants, resets a password, and <strong>deletes</strong> a user — an explicit second
step, available only after disabling, so removal is never a single misclick.
A new Settings → Account tab lets any signed-in local user rotate their own
password: the current password is required, other sessions are evicted, and
the one you are using stays alive. Password operations are refused for SSO
users, whose credential lives at the identity provider.</p>
</li>
<li class="">
<p><strong>An admin could mint a working local password for an SSO-only account.</strong>
<code>PUT /api/v1/users/{id}</code> accepted a password for any user regardless of
origin, and the login path never filtered on origin either — so the new
credential was a real, working login that walked around the identity provider
along with its MFA and conditional-access policy. Password edits are now
allow-listed on local accounts, on both the admin route and the new
self-service one.</p>
</li>
<li class="">
<p><strong>Rotating IP addresses walked straight past the login lockout.</strong> Both
rate-limit axes keyed on the client address, so guesses spread across N
addresses bought N × 5 attempts per minute against one account and tripped
neither — any cloud NAT pool was enough to make the per-account lockout
decorative. A third axis now counts failures against the account itself
(20 per minute), for password login and self-service change alike. It is a
deliberate trade: sustained failures will keep that account's <em>login</em> blocked
for a self-healing one-minute window, never touching established sessions or
successful logins.</p>
</li>
<li class="">
<p><strong>An SSO login could take over a local account's email.</strong> An identity-provider
user whose email matched a local account added a second row sharing that
address, and the password lookup then resolved to an arbitrary one of the two
— so anyone able to set their own email claim could aim it at the bootstrap
admin. The lookup is now local-first, and password login is allow-listed on
local accounts rather than trusting SSO rows to carry an empty password hash.</p>
</li>
<li class="">
<p><strong>An install whose schema migration never ran now repairs itself.</strong> Schema is
applied by a Helm <code>post-install</code>/<code>post-upgrade</code> hook — and Helm runs those
only <em>after</em> <code>--wait</code> succeeds, so a release that timed out on any slow
component never created the job, while the deployments Helm had already
applied rolled out normally. The result looked healthy and answered <em>unknown
table</em> to everything. The hub now checks its schema on connect and applies
what is missing itself (<code>hub.autoMigrate</code>, on by default; the migrations are
idempotent). When it cannot — no DDL rights, or self-heal switched off — it
logs <strong>one</strong> error naming the remedy instead of a warning flood, and
Settings → Status gains a <strong>Schema</strong> component showing applied versus
expected.</p>
</li>
<li class="">
<p><strong>A non-default ClickHouse database name no longer breaks the install
silently.</strong> <code>clickhouse.external.database</code> is documented and schema-checked,
but every migration hardcoded the <code>otel</code> prefix: the tables landed in one
database while the hub queried another and found it empty. Migrations now
name their database through a placeholder, and the configured name is
validated as an identifier at boot. Installs on the default are unaffected.</p>
</li>
<li class="">
<p><strong>Green no longer takes the sensor down on nodes without RAPL.</strong> The measured
power source exits at startup when it finds no powercap zones, and a container
that terminates itself keeps the whole pod out of Ready — so enabling green
collection on a fleet of VMs took logs, traces and metrics down with an
optional energy signal. The measured source can now be dropped on its own
with <code>sensor.green.kepler.enabled=false</code>, leaving TDP estimation to feed the
Green page. RAPL hardware is unaffected.</p>
</li>
<li class="">
<p><strong>Logging in through a reverse proxy that rewrites <code>Host</code> no longer 403s.</strong>
The CSRF check compared the browser's <code>Origin</code> against the <code>Host</code> the hub
received, so a proxy handing the cluster its ingress address turned every
write — the login POST first of all — into a rejected cross-origin request.
<code>auth.trustedOrigins</code> names the origins that are legitimate despite not
matching <code>Host</code>, and <code>auth.originCheck</code> (<code>enforce</code> | <code>log</code> | <code>off</code>) lowers the
check when they cannot be enumerated; <code>log</code> allows the write and records the
<code>Origin</code>/<code>Host</code> pair, which is how you find out what your proxy actually
sends. The default stays strict, and an OIDC <code>publicUrl</code> is trusted
automatically.</p>
</li>
</ul>
<p><strong>Upgrading from v0.3.x</strong> is a normal <code>helm upgrade</code>. Schema migration <code>0015</code>
applies automatically. If you run a hardened ClickHouse user without DDL rights,
keep the migrate job as-is and leave <code>hub.autoMigrate</code> on — it will report
cleanly rather than fail. See the <a class="" href="https://avuruobs.io/docs/releases">Releases</a> page and the
<a href="https://github.com/avuruvision/avuru-obs/releases/tag/v0.4.0" target="_blank" rel="noopener noreferrer" class="">GitHub release</a>.</p>]]></content:encoded>
            <category>Platform</category>
            <category>UI</category>
            <category>Security</category>
        </item>
        <item>
            <title><![CDATA[v0.3.0 is out: tenancy you can trust — projects you administer, telemetry that proves whose it is]]></title>
            <link>https://avuruobs.io/changelog/v0-3-0</link>
            <guid>https://avuruobs.io/changelog/v0-3-0</guid>
            <pubDate>Fri, 31 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The third release closes the write side: create, rename and delete projects from the UI, then mint per-project ingest keys so the key — not the sender — decides where telemetry lands. Plus a one-click read-only demo, green energy on RAPL-less cloud VMs, and the deploy layer renamed to avuruobs.]]></description>
            <content:encoded><![CDATA[<p>avuru obs <strong>v0.3.0</strong> is tagged. v0.2 secured the read side — login, roles,
per-project grants, SSO. v0.3 closes the write side and turns the project into
something you actually administer.</p>
<!-- -->
<ul>
<li class=""><strong>Projects you administer.</strong> Create, rename and delete projects in
<strong>Settings → General</strong>; the switcher and the General tab follow immediately,
while the built-in <code>default</code> and any project declared in the chart stay
read-only and clearly labelled. A project's id is an immutable tenant slug —
only its display name is editable, so no telemetry is ever rewritten or
lost.</li>
<li class=""><strong>Telemetry that proves whose it is.</strong> Mint <strong>per-project ingest API keys</strong>;
the raw secret is shown exactly once and only its hash is stored. Keys are
validated in the gateway — the hub never enters the telemetry byte-path — and
in <code>enforce</code> mode the key's project becomes the <strong>authoritative tenant</strong>: a
sender that lies about its tenant lands where its key says. The default <code>log</code>
mode leaves the pipeline byte-identical, so the drop-in OTLP promise survives
the upgrade.</li>
<li class=""><strong>A demo you can hand to anyone.</strong> One click on the login page signs a
visitor in as a read-only viewer scoped to a single project; the shared
password stays server-side and never reaches the browser. Pair it with the
astronomy-shop overlay for live data across every module.</li>
<li class=""><strong>Green on cloud VMs.</strong> Public-cloud instances expose no power counters, so
an opt-in power model now fills the gap — every modeled number labeled
<em>estimated</em> end to end, never blended with measured energy, with a new
coverage panel that makes the RAPL-less share of a fleet visible
(<a class="" href="https://avuruobs.io/changelog/green-tdp-estimation">details</a>).</li>
<li class=""><strong>Runtime collection control — groundwork.</strong> The hub can now store and serve
a bounded, schema-validated collection overlay behind a default-off flag and
a least-privilege Role. The applier and the editable UI come next; collection
is still configured through Helm values today.</li>
<li class=""><strong>Breaking: <code>avuruops</code> is now <code>avuruobs</code>.</strong> The Helm chart, the <code>AVURUOBS_*</code>
environment variables, the mount paths and the generated resource names all
match the project's name now. Upgrading from 0.2.x is <strong>not</strong> a plain <code>helm upgrade</code> — follow the <a class="" href="https://avuruobs.io/docs/operations/upgrade">upgrade guide</a>, which gives
you a no-rename path (<code>--set nameOverride=avuruops</code>) and a clean-install
path.</li>
</ul>
<p>Also fixed: a fresh install with the demo enabled could end up with <strong>no admin
account</strong> (the demo viewer satisfied the bootstrap guard, so <code>admin</code> was never
created and every sign-in failed as if the password were wrong) — affected
installs repair themselves on the next restart. Plus a first-install Helm
render failure, the demo visitor landing on the wrong project, and login behind
a reverse proxy on a non-default port.</p>
<p>See the full notes on the <a class="" href="https://avuruobs.io/docs/releases">Releases</a> page and the
<a href="https://github.com/avuruvision/avuru-obs/releases/tag/v0.3.0" target="_blank" rel="noopener noreferrer" class="">GitHub release</a>.
The trunk is now <code>0.4.0-SNAPSHOT</code> — next up on the
<a class="" href="https://avuruobs.io/docs/roadmap">Roadmap</a>: multi-cluster member projects, the collection control
plane finished end to end, and wider ingest compatibility.</p>]]></content:encoded>
            <category>Platform</category>
            <category>UI</category>
            <category>Sustainability</category>
        </item>
        <item>
            <title><![CDATA[v0.3.1: a default install pulls its images again]]></title>
            <link>https://avuruobs.io/changelog/v0-3-1</link>
            <guid>https://avuruobs.io/changelog/v0-3-1</guid>
            <pubDate>Fri, 31 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A patch release. The chart's image coordinates never matched what the release workflow publishes, so a helm install with no --set could not resolve the hub, UI, gateway or TDP-estimator images. Fixed on both halves.]]></description>
            <content:encoded><![CDATA[<p><strong>v0.3.1</strong> is a patch: it fixes the chart's image defaults, which never pointed
at the images the release workflow actually publishes. Nothing else changes —
no schema migration, no API or configuration surface.</p>
<!-- -->
<ul>
<li class=""><strong>Both halves of the reference were wrong.</strong> The repositories named Docker
Hub coordinates that nothing publishes, while releases push to
<code>ghcr.io/avuruvision/avuru-obs-&lt;component&gt;</code>; and the tag defaulted to the
chart's <code>appVersion</code> — bare SemVer, no leading <code>v</code> — while only <code>vX.Y.Z</code> and
<code>vX.Y</code> were ever pushed. Fixing one without the other would only have
swapped one unresolvable reference for another, so both are fixed: the four
first-party repositories point at the registry, and the release workflow now
also publishes the bare <code>X.Y.Z</code> tag.</li>
<li class=""><strong>Green TDP estimation had no image at all.</strong> Its repository shipped empty,
so enabling the estimator rendered a reference with no name and no tag. It
now defaults like the other three first-party images.</li>
</ul>
<p><strong>If you are on v0.3.0</strong>, <code>helm upgrade</code> to <code>0.3.1</code> and you can drop any
<code>--set hub.repository=…</code> / <code>--set …tag=…</code> you added to work around this.
Installs that pin images deliberately — a private registry through
<code>image.registry</code>, or air-gapped mirrors — are unaffected either way.</p>
<p>See the <a class="" href="https://avuruobs.io/docs/releases">Releases</a> page and the
<a href="https://github.com/avuruvision/avuru-obs/releases/tag/v0.3.1" target="_blank" rel="noopener noreferrer" class="">GitHub release</a>.</p>]]></content:encoded>
            <category>Platform</category>
        </item>
        <item>
            <title><![CDATA[Energy numbers on cloud VMs: TDP estimation for RAPL-less nodes]]></title>
            <link>https://avuruobs.io/changelog/green-tdp-estimation</link>
            <guid>https://avuruobs.io/changelog/green-tdp-estimation</guid>
            <pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Public-cloud instances expose no power counters, so the green module used to leave them blank. An opt-in power model now fills the gap — and every modeled number is labeled estimated end to end, never blended with measured energy.]]></description>
            <content:encoded><![CDATA[<p>The green module reported energy only where the hardware exposes RAPL — which
excludes the overwhelming majority of public-cloud VMs. An <strong>opt-in TDP
estimator</strong> now models CPU power from utilization on those nodes, so a fleet
that runs entirely on cloud instances stops seeing an empty <code>/green</code>.</p>
<!-- -->
<ul>
<li class=""><strong>Honest by construction.</strong> Every number the estimator produces is stamped
<strong>estimated</strong> through the whole chain — the SQL, the API, the UI and the
CSRD export's methodology block — and is never blended with RAPL-measured
energy. Estimated and measured energy for the same service appear as
separate rows, so a total can never quietly mix the two. Treat estimates as
trend and regression grade (±30-50% typical error), not audit grade.</li>
<li class=""><strong>The RAPL-less share becomes visible.</strong> <code>/green</code> gains a coverage panel
breaking the fleet into known, measured, estimated and absent nodes. What
used to be silently missing is now something you can see and act on.</li>
<li class=""><strong>Budgets account for it.</strong> Carbon budgets include estimated energy, so an
all-VM fleet's budget can actually trip — and a breach states how much of it
is modeled versus measured.</li>
<li class=""><strong>Sourced coefficients.</strong> The bundled CPU power table is cited (Cloud Carbon
Footprint, cross-checked against the SPECpower-derived source notebook), and
operators can override <code>P_idle</code>/<code>P_max</code> per node or fleet-wide.</li>
</ul>
<p>Enable it with <code>sensor.green.estimation.enabled</code> (it requires
<code>sensor.green.enabled</code> — the estimator is a fallback for the same energy
signal, not a second source). See the
<a class="" href="https://avuruobs.io/docs/signals/green">green module docs</a>.</p>]]></content:encoded>
            <category>Sustainability</category>
            <category>Platform</category>
        </item>
        <item>
            <title><![CDATA[Authenticate your telemetry: per-project ingest API keys]]></title>
            <link>https://avuruobs.io/changelog/ingest-api-keys</link>
            <guid>https://avuruobs.io/changelog/ingest-api-keys</guid>
            <pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Telemetry is now authenticated at the write side. A key belongs to a project, and in enforce mode that project becomes the authoritative tenant — a sender can no longer decide for itself which project it lands in. Rolled out through a safe three-stage dial that keeps existing senders working.]]></description>
            <content:encoded><![CDATA[<p>Until now, a gateway believed whatever project a sender claimed. Now you can
issue <strong>per-project ingest API keys</strong> and have the key decide instead — with a
default that changes nothing until you are ready.</p>
<!-- -->
<ul>
<li class=""><strong>The key decides the project.</strong> Mint keys in <strong>Settings → General → Ingest
API keys</strong> (or <code>POST /api/v1/projects/{project}/keys</code>). In <code>enforce</code> mode the
validated key's project becomes the <strong>authoritative tenant</strong>, overriding any
value the sender supplied. A misconfigured — or dishonest — sender can no
longer write into a project it was never granted.</li>
<li class=""><strong>The secret is shown exactly once.</strong> Only a SHA-256 hash is stored, so the
raw key cannot be recovered from the database or the API. The list view shows
a short prefix and metadata for identification; revoking is immediate.</li>
<li class=""><strong>Your ingest path stays fast.</strong> Keys are validated <strong>in the gateway</strong>, by a
purpose-built collector extension. The hub answers a control-plane question
and is never in the telemetry byte-path. Verdicts are cached, and a cached
verdict keeps serving through a hub restart — a control-plane blip cannot
drop your traffic.</li>
<li class=""><strong>A three-stage rollout, not a switch.</strong> <code>auth.ingest.mode</code> moves through
<code>off</code> → <code>log</code> → <code>enforce</code>. <strong><code>log</code> is the default</strong>: keys are validated and
would-be denials counted, but nothing is rejected and the pipeline is
byte-identical to before. That gives you a precise list of senders that would
break <em>before</em> they do. Flip to <code>enforce</code> once it reads zero.</li>
<li class=""><strong>Existing senders keep working.</strong> Upgrading changes nothing on its own — the
drop-in promise holds. The chart also provisions and seeds a key for the node
sensor automatically, so turning on <code>enforce</code> never silences your own agent.</li>
</ul>]]></content:encoded>
            <category>Platform</category>
            <category>UI</category>
        </item>
        <item>
            <title><![CDATA[Manage projects from the UI: create, rename and delete]]></title>
            <link>https://avuruobs.io/changelog/project-management</link>
            <guid>https://avuruobs.io/changelog/project-management</guid>
            <pubDate>Wed, 29 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Projects now have a real identity you control in the app — admins create, rename and delete them in Settings → General, while the built-in and config-defined projects stay safely read-only. No telemetry is ever rewritten.]]></description>
            <content:encoded><![CDATA[<p>Projects are no longer only a deployment setting: admins can <strong>create, rename
and delete</strong> them directly in <strong>Settings → General</strong>, and the switcher updates
to match — while the built-in <code>default</code> and config-defined projects stay
read-only.</p>
<!-- -->
<ul>
<li class=""><strong>Create, rename, delete — from the app.</strong> An admin-only project card on
Settings → General adds new projects, edits a project's display name, and
deletes UI-managed ones. The sidebar switcher and General tab reflect changes
immediately. Non-admins keep read-only visibility of the projects they're
granted.</li>
<li class=""><strong>Deployment-owned projects stay protected.</strong> The built-in <code>default</code> project
and any project declared in the chart (<code>projects</code> value / <code>AVURUOBS_PROJECTS</code>)
are clearly marked and <strong>cannot be edited or deleted from the UI</strong> — the app
refuses with a <code>409</code>, so your source-of-truth config always wins.</li>
<li class=""><strong>A stable id, an editable name.</strong> A project's id is an <strong>immutable tenant
slug</strong> — the partition key on every trace, log and metric; only its display
<strong>label</strong> is editable. Renaming or deleting <strong>never rewrites telemetry</strong>: a
delete removes the entry and its data ages out by the existing retention, and
a still-active tenant simply reappears as auto-discovered.</li>
<li class=""><strong>Admin-enforced, server-side.</strong> The new <code>POST</code> / <code>PUT</code> / <code>DELETE /api/v1/projects</code> endpoints require a global admin (<code>403</code> otherwise), and
<code>GET /api/v1/projects</code> now returns each project's <code>source</code>, <code>label</code> and
<code>editable</code> flag. See the <a class="" href="https://avuruobs.io/reference/api">API reference</a>.</li>
<li class=""><strong>Groundwork for what's next.</strong> This is the first slice of the projects work;
per-project ingest keys and multi-cluster aggregate views build on the same
model.</li>
</ul>]]></content:encoded>
            <category>UI</category>
            <category>Platform</category>
        </item>
        <item>
            <title><![CDATA[See it before you install it: a one-click read-only demo]]></title>
            <link>https://avuruobs.io/changelog/read-only-demo</link>
            <guid>https://avuruobs.io/changelog/read-only-demo</guid>
            <pubDate>Wed, 29 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[A single 'Try the demo' button drops anyone into a live, read-only avuru obs — real traces, logs, metrics, errors, service map and energy across a running multi-service app, with no signup and no setup.]]></description>
            <content:encoded><![CDATA[<p>You can now explore a <strong>live avuru obs</strong> without installing anything: one click
on <strong>"Try the demo"</strong> signs you in as a read-only viewer against a running
multi-service app, with real data across every module.</p>
<!-- -->
<ul>
<li class=""><strong>One click, zero setup.</strong> A <strong>"Try the demo"</strong> button on the login page signs
a visitor straight in as a read-only <strong>viewer</strong> scoped to a <code>demo</code> project — no
account to create, nothing to install. The rest of the app behaves exactly as
it would for a real user; write actions (triage, settings, user admin) simply
aren't offered.</li>
<li class=""><strong>Real data, every module.</strong> The demo is fed by the live <strong>OpenTelemetry
Astronomy Shop</strong> — a genuine multi-service application — so the service map,
traces, logs, RED metrics, error tracking, service health and energy views all
show real, moving data instead of static screenshots.</li>
<li class=""><strong>Safe by construction.</strong> The demo is a single low-privilege identity: the
shared password stays <strong>server-side</strong> (a rate-limited sign-in endpoint), never
reaching the browser, and the whole feature is <strong>opt-in</strong> — a normal install
ships no demo user and no demo project. Enable it with <code>auth.demo.enabled</code>.</li>
<li class=""><strong>Also in this release.</strong> The Settings <strong>Users</strong> tab now opens in place instead
of navigating away, so the tab bar no longer disappears; the login wordmark
reads <strong>Avuru Obs</strong>. See the <a class="" href="https://avuruobs.io/reference/api">API reference</a> for the new
demo sign-in endpoint.</li>
</ul>]]></content:encoded>
            <category>UI</category>
            <category>Platform</category>
        </item>
        <item>
            <title><![CDATA[Sign in with your company IdP: authentication, roles and OIDC SSO]]></title>
            <link>https://avuruobs.io/changelog/authentication-sso</link>
            <guid>https://avuruobs.io/changelog/authentication-sso</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[avuru obs is now secure by default: local users with Admin/Editor/Viewer roles granted per project, server-side sessions, and enterprise SSO with any OpenID Connect identity provider — in open source, with no extra proxy to run.]]></description>
            <content:encoded><![CDATA[<p>avuru obs now ships <strong>secure by default</strong>: the hub requires login, users carry
fixed roles — <strong>Admin, Editor, Viewer</strong> — granted <strong>per project</strong>, and
<strong>enterprise SSO</strong> works with any OpenID Connect identity provider (Keycloak,
Microsoft Entra ID, Okta, Google, Dex) — in open source, not behind an
enterprise tier, and with no auth proxy to deploy.</p>
<!-- -->
<ul>
<li class=""><strong>Secure by default.</strong> New installs require login (<code>auth.enabled=true</code>); Helm
bootstraps an <code>admin</code> user whose password lives in the release Secret. Every
API route is enforced <strong>server-side</strong>, and the project header is validated
against the caller's grants — projects become a real security boundary: a
user granted only <code>staging</code> gets <code>403</code> anywhere else and a project switcher
that lists only <code>staging</code>. <code>auth.enabled=false</code> restores the previous open
behavior. See <a class="" href="https://avuruobs.io/docs/operations/security">Security</a>.</li>
<li class=""><strong>Roles × projects, not an ACL maze.</strong> Three fixed roles — Admin, Editor,
Viewer — granted per project (or <code>*</code> for all). A login page and a
<strong>Settings → Users</strong> admin screen come with it; the endpoint surface is in
the <a class="" href="https://avuruobs.io/reference/api">API reference</a>.</li>
<li class=""><strong>Sign in with your company IdP.</strong> The hub runs the OIDC
<strong>authorization-code + PKCE</strong> flow itself (<code>/api/v1/auth/oidc/start</code> → IdP →
<code>/api/v1/auth/oidc/callback</code>) — no extra pod, no proxy in front. Provider
discovery is <strong>fail-loud</strong> at startup, so a wrong issuer stops the rollout
instead of shipping a broken login, and an SSO login ends in the same
server-side session as a local one.</li>
<li class=""><strong>Group-based access, zero per-user admin.</strong> <code>auth.oidc.mapping</code> maps IdP
groups to a role on projects declaratively (with a <code>defaultRole</code> fallback).
Groups are refreshed at each SSO sign-in and the mapping is applied on every
request from the <strong>hot-reloaded</strong> config file (~15 s, no restart) — an access
policy change takes effect without touching individual accounts.</li>
<li class=""><strong><code>forceSSO</code> for centralized policy.</strong> Hides the local password form so the
IdP is the only door for people — the local admin API login stays available
as break-glass. Everything is configured from Helm values (<code>auth.oidc.*</code>);
the client secret comes from your own Secret or a chart-managed one, never
the config file.</li>
<li class=""><strong>Sessions you can actually revoke.</strong> Sessions are <strong>server-side</strong>
(revocation is immediate), logins are rate-limited, and state lives in the
existing database — no new components.</li>
<li class=""><strong>Opt-in anonymous viewing.</strong> Grant visitors a role on an <strong>explicit project
list</strong> only: a public demo can share one project while every other project
stays invisible.</li>
</ul>]]></content:encoded>
            <category>Platform</category>
            <category>UI</category>
        </item>
        <item>
            <title><![CDATA[Open for good: the AGPL license, stated in full]]></title>
            <link>https://avuruobs.io/changelog/licensing-model</link>
            <guid>https://avuruobs.io/changelog/licensing-model</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[avuru obs now spells out its licensing in plain terms — AGPL-3.0 for the platform, Apache-2.0 for the eBPF sensor, a CLA that pledges every contribution stays open forever, and generated third-party attribution. Self-hosting stays zero-obligation.]]></description>
            <content:encoded><![CDATA[<p>avuru obs is <strong>AGPL-3.0</strong>, and the whole licensing story now lives in one place —
what it asks of you (almost nothing), why the project chose it, and how it stays
sustainable <strong>without gating features behind a paid tier</strong>.</p>
<!-- -->
<ul>
<li class=""><strong>A licensing page you can actually read.</strong> A new
<a class="" href="https://avuruobs.io/docs/licensing">License &amp; sustainability</a> page (EN + FR) answers the
questions people actually have: self-hosting is <strong>zero-obligation</strong>, observing
your services never makes them derivative works, and the AGPL only asks
something of you if you <strong>modify avuru obs itself and offer it to third parties
over a network</strong>.</li>
<li class=""><strong>Nothing is gated.</strong> Authentication, roles, per-project access control and
<strong>OIDC SSO</strong> — capabilities frequently reserved for paid tiers elsewhere —
are in the open edition. AGPL-3.0, forever.</li>
<li class=""><strong>Every contribution stays open.</strong> The
<a href="https://github.com/avuruvision/avuru-obs/blob/main/CLA.md" target="_blank" rel="noopener noreferrer" class="">Contributor License Agreement</a>
§2.2 legally pledges that every contribution remains available under
AGPL-3.0 — the open edition can never be closed or stripped down
retroactively.</li>
<li class=""><strong>The sensor stays Apache-2.0.</strong> The eBPF node agent is upstream
<a href="https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation" target="_blank" rel="noopener noreferrer" class="">OpenTelemetry eBPF Instrumentation</a>
(OBI), configured — not forked — so it keeps its permissive license.</li>
<li class=""><strong>Third-party attribution, generated not hand-kept.</strong> A <code>make notices</code> target
produces
<a href="https://github.com/avuruvision/avuru-obs/blob/main/THIRD-PARTY-NOTICES.md" target="_blank" rel="noopener noreferrer" class="">THIRD-PARTY-NOTICES.md</a>
from the actual Go and TypeScript dependency trees, wired into the release
checklist so attribution never drifts from what ships.</li>
<li class=""><strong>Why the AGPL, said out loud.</strong> An observability platform holds the most
sensitive telemetry an organization produces. avuru obs is built as a
<strong>sovereign</strong> alternative to per-host SaaS billing — and the AGPL is what
keeps the open edition from being enclosed into someone else's closed service.</li>
</ul>]]></content:encoded>
            <category>Platform</category>
            <category>Docs</category>
        </item>
        <item>
            <title><![CDATA[v0.2.0 is out: depth and control — secure by default, modular signals, four new modules]]></title>
            <link>https://avuruobs.io/changelog/v0-2-0</link>
            <guid>https://avuruobs.io/changelog/v0-2-0</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The second release makes the five-minute install safe to run for real teams: login with roles and SSO, pick-your-signals modules, a sensor that is provably safe to leave on, and error tracking, service health, alerting and green energy on the data you already collect.]]></description>
            <content:encoded><![CDATA[<p>avuru obs <strong>v0.2.0</strong> is tagged. v0.1 proved the wedge — a live service map in
under five minutes, zero app changes. v0.2 makes that install one a real team
can run every day: <strong>secure by default</strong>, <strong>modular by design</strong>, and four new
modules that turn the data you already collect into triage, status and
accountability.</p>
<!-- -->
<ul>
<li class=""><strong>Secure by default.</strong> The hub now requires login: local users with Admin,
Editor and Viewer roles granted per project, enforced server-side on every
API route — projects become a real security boundary, not a filter. Add
<strong>enterprise SSO</strong> with any OpenID Connect IdP (group→role mapping,
<code>forceSSO</code> for IdP-only fleets) — no proxy sidecar, no extra pod
(<a class="" href="https://avuruobs.io/docs/operations/security">security guide</a>).</li>
<li class=""><strong>Pick your signals.</strong> One switch per signal family
(<code>modules.&lt;name&gt;.enabled</code>) gates its schema, API, pipeline, collection and
UI together — a traces-only install carries no log or profile weight, and
the sidebar follows what the install advertises.</li>
<li class=""><strong>Error tracking.</strong> Exceptions already in your spans and logs become
deduplicated, triageable issues — stack trace, occurrence timeline, link to
the originating trace, resolved/ignored lifecycle with regression detection.
Browser apps report too, by changing one DSN — the signal eBPF cannot reach.</li>
<li class=""><strong>Service health &amp; alerting.</strong> Group services with criticality tiers
(T0/T1/T2) and dependency propagation — a service can't read green while a
critical dependency is red — then get a webhook (SSRF-guarded, with
firing/resolved lifecycle) when a group crosses into a bad state.</li>
<li class=""><strong>Network health on the map.</strong> Per-edge RTT and failed/reset connection
counts from eBPF TCP stats — connection-level health without traces or SDKs.</li>
<li class=""><strong>Green: energy &amp; carbon.</strong> Per-service Wh and gCO2e measured at the node
(opt-in, RAPL hardware), monthly carbon budgets with alerts, and a
CSRD-ready export that states its methodology. Nothing leaves the cluster.</li>
<li class=""><strong>A sensor that is provably safe to leave on.</strong> CI keeps a probe-sensitive
canary healthy through a soak with the sensor attached; cautious fleets get
an opt-in discovery mode and a staged-rollout runbook.</li>
<li class=""><strong>AGPL-3.0.</strong> The project is now licensed AGPL-3.0.</li>
</ul>
<p>See the full notes on the <a class="" href="https://avuruobs.io/docs/releases">Releases</a> page and the
<a href="https://github.com/avuruvision/avuru-obs/releases/tag/v0.2.0" target="_blank" rel="noopener noreferrer" class="">GitHub release</a>.
The trunk is now <code>0.3.0-SNAPSHOT</code> — next up on the
<a class="" href="https://avuruobs.io/docs/roadmap">Roadmap</a>: per-project ingest keys, the runtime collection
control plane, and wider ingest compatibility.</p>]]></content:encoded>
            <category>Platform</category>
            <category>UI</category>
            <category>Sustainability</category>
        </item>
        <item>
            <title><![CDATA[Green: per-service energy and carbon, CSRD-ready and offline]]></title>
            <link>https://avuruobs.io/changelog/green</link>
            <guid>https://avuruobs.io/changelog/green</guid>
            <pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Per-service energy (Wh) and carbon (gCO2e) from CNCF Kepler, correlated with the map you already have — carbon budgets, per-request intensity, and an auditable CSRD export. Zero code changes, no data leaves the cluster.]]></description>
            <content:encoded><![CDATA[<p>The new <strong>green</strong> module turns the pod→service map you already run into
<strong>per-service energy (Wh) and carbon (gCO2e)</strong> — the numbers ESRS E1 / CSRD and
internal sustainability targets ask for, without a spreadsheet estimate or a
SaaS that wants your telemetry. Energy is measured by <strong>CNCF Kepler</strong> (from the
CPU's RAPL counters); avuru obs already knows which pod is which service, so it
attributes joules to the services on your map. Self-hosted, zero app changes,
no external API.</p>
<!-- -->
<ul>
<li class=""><strong>Numbers an auditor can reproduce.</strong> A CSRD-ready CSV/JSON export carries a
<strong>methodology block</strong> — the formula, the grid-intensity factor and its
provenance, the measurement coverage ratio, and an explicit <em>unattributed</em>
bucket — so a reported gCO2e is traceable back to how it was produced.</li>
<li class=""><strong>Carbon budgets per team.</strong> Monthly gCO2e budgets per service group, with
<strong>warn at 80%</strong>, <strong>exceeded at 100%</strong>, and a month-end projection — delivered
through the <a class="" href="https://avuruobs.io/docs/signals/alerting">alerting</a> channels you already
configured. With alerting off, budgets still show status on the dashboard.</li>
<li class=""><strong>Efficiency you can prove.</strong> Per-request carbon intensity (Wh ÷ requests,
gCO2e derived) and a per-service trend, so an optimization shows up even as
traffic grows — plus a <strong>gCO2e overlay</strong> on the service map you already watch.</li>
<li class=""><strong>Offline by design.</strong> Grid-intensity factors are <strong>bundled per-country
annual averages</strong> with operator overrides in config; <code>gCO2e = Wh × intensity × PUE</code> runs in the hub at query time. No egress, air-gap friendly.</li>
<li class=""><strong>No new storage, no code changes.</strong> All math runs over the metric tables
that already exist — no migration. Kepler joins the sensor as an <strong>opt-in,
probe-free</strong> container (<code>sensor.green.enabled</code>), so it can never destabilize
the sensor pod, and on nodes without RAPL the module reports honestly (a
coverage ratio and a teaching empty state) rather than estimating.</li>
<li class=""><strong>Off by default.</strong> The signal depends on RAPL/powercap hardware, so
<code>modules.green.enabled</code> ships <strong>off</strong> — an existing install upgrades
unchanged. It requires the <a class="" href="https://avuruobs.io/docs/setup/modules">infra-metrics module</a> for
the pod→workload join.</li>
</ul>
<p>:::caution Confirm on real hardware before production
Kepler's metric names, config keys and port are validated in CI against the
pinned image's dev meter, but <strong>must be confirmed on real RAPL hardware before
production use</strong>. The hub's metric names are configurable for exactly this
reason. See the <a class="" href="https://avuruobs.io/docs/signals/green">Green</a> page.
:::</p>]]></content:encoded>
            <category>Sustainability</category>
            <category>Platform</category>
        </item>
    </channel>
</rss>