<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Deevnet API</title><link>https://deevnet.github.io/deevnet-provisioning-api/</link><description>Recent content on Deevnet API</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://deevnet.github.io/deevnet-provisioning-api/index.xml" rel="self" type="application/rss+xml"/><item><title>API Reference</title><link>https://deevnet.github.io/deevnet-provisioning-api/docs/reference/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://deevnet.github.io/deevnet-provisioning-api/docs/reference/</guid><description/></item><item><title>Authentication</title><link>https://deevnet.github.io/deevnet-provisioning-api/docs/authentication/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://deevnet.github.io/deevnet-provisioning-api/docs/authentication/</guid><description>&lt;h1 id="authentication">
 Authentication
 &lt;a class="anchor" href="#authentication">#&lt;/a>
&lt;/h1>
&lt;p>&lt;strong>Every &lt;code>/v1&lt;/code> request carries &lt;code>Authorization: Bearer &amp;lt;token&amp;gt;&lt;/code>.&lt;/strong> A request without a token the
API recognizes answers &lt;code>401&lt;/code> with &lt;code>WWW-Authenticate: Bearer realm=&amp;quot;deevnet-api&amp;quot;&lt;/code>. The three
service routes, &lt;code>/healthz&lt;/code>, &lt;code>/readyz&lt;/code> and &lt;code>/version&lt;/code>, take no token.&lt;/p>
&lt;h2 id="tokens">
 Tokens
 &lt;a class="anchor" href="#tokens">#&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>The token is one of four things, and it decides what the caller may do.&lt;/strong>&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Token&lt;/th>
 &lt;th>What it is&lt;/th>
 &lt;th>May&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;strong>Operator&lt;/strong>&lt;/td>
 &lt;td>the site&amp;rsquo;s operator token&lt;/td>
 &lt;td>everything&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Tenant&lt;/strong>&lt;/td>
 &lt;td>&lt;code>dvt1.&amp;lt;tenant&amp;gt;.&amp;lt;nonce&amp;gt;.&amp;lt;mac&amp;gt;&lt;/code>, returned when the tenant is created&lt;/td>
 &lt;td>read and delete its own tenant, restore itself, and manage its own workloads, names, devices, keys, accounts and certificates&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Enrollment&lt;/strong>&lt;/td>
 &lt;td>single-use, issued by an admission&lt;/td>
 &lt;td>create the one tenant it was issued for, once&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;strong>Egress agent&lt;/strong>&lt;/td>
 &lt;td>the exit node&amp;rsquo;s token&lt;/td>
 &lt;td>read &lt;code>GET /v1/fabric/egress&lt;/code>, nothing else&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>A tenant holds exactly one of these at a time: the enrollment token for its first apply, then its
own token for every later one.&lt;/p></description></item><item><title>Tenant Lifecycle</title><link>https://deevnet.github.io/deevnet-provisioning-api/docs/tenant-lifecycle/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://deevnet.github.io/deevnet-provisioning-api/docs/tenant-lifecycle/</guid><description>&lt;h1 id="tenant-lifecycle">
 Tenant lifecycle
 &lt;a class="anchor" href="#tenant-lifecycle">#&lt;/a>
&lt;/h1>
&lt;p>&lt;strong>A tenant is admitted by the operator, then creates itself.&lt;/strong> Everything after admission is the
tenant&amp;rsquo;s own call, made with its own token
(
 &lt;a href="https://deevnet.github.io/deevnet-docs/docs/architecture/decisions/tenant-model/0015-tenant-onboarding-through-api/">ADR-0015&lt;/a>).&lt;/p>
&lt;h2 id="admission">
 Admission
 &lt;a class="anchor" href="#admission">#&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>&lt;code>POST /v1/admissions&lt;/code> reserves a name and issues a single-use enrollment token.&lt;/strong> Only the
operator may call it, and the operator hands the token to the tenant over a channel trusted with
a password.&lt;/p>
&lt;p>At a site with a tenant developer network, the answer also carries a Wi-Fi key
(
 &lt;a href="https://deevnet.github.io/deevnet-docs/docs/architecture/decisions/tenant-networking/0029-tenant-developer-network-keys/">ADR-0029&lt;/a>). The tenant needs that
network to reach the API at all, so the key cannot come from the tenant&amp;rsquo;s own apply.&lt;/p></description></item><item><title>Device Services</title><link>https://deevnet.github.io/deevnet-provisioning-api/docs/device-services/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://deevnet.github.io/deevnet-provisioning-api/docs/device-services/</guid><description>&lt;h1 id="device-services">
 Device services
 &lt;a class="anchor" href="#device-services">#&lt;/a>
&lt;/h1>
&lt;p>&lt;strong>A tenant&amp;rsquo;s devices get four things from the API: a registry entry, a fixed address, a Wi-Fi
key and a broker account.&lt;/strong> Each is its own resource, and none implies another. The

 &lt;a href="https://deevnet.github.io/deevnet-provisioning-api/deevnet-provisioning-api/docs/reference/">API Reference&lt;/a> has every field; this page is what the fields do not say.&lt;/p>
&lt;h2 id="registry">
 Registry
 &lt;a class="anchor" href="#registry">#&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>A registry entry is identity, not authorization.&lt;/strong> Registering a device grants it nothing. What
a device may consume is carried by a credential it proves
(
 &lt;a href="https://deevnet.github.io/deevnet-docs/docs/architecture/decisions/edge-devices/0020-direct-device-access-to-tenant-services/">ADR-0020&lt;/a> §2), and this resource
issues none. Nor does a device reach its tenant&amp;rsquo;s network.&lt;/p></description></item><item><title>Service Certificates</title><link>https://deevnet.github.io/deevnet-provisioning-api/docs/service-certificates/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://deevnet.github.io/deevnet-provisioning-api/docs/service-certificates/</guid><description>&lt;h1 id="service-certificates">
 Service certificates
 &lt;a class="anchor" href="#service-certificates">#&lt;/a>
&lt;/h1>
&lt;p>&lt;strong>A tenant&amp;rsquo;s own service gets a server certificate from the API, for a name in the tenant&amp;rsquo;s own
zone, and the tenant keeps the key.&lt;/strong> The site&amp;rsquo;s Tenant Service CA signs it
(
 &lt;a href="https://deevnet.github.io/deevnet-docs/docs/architecture/decisions/substrate/0037-tenant-service-certificates/">ADR-0037&lt;/a>).
It chains to the Deevnet Root CA, which every client on a site already trusts. The

 &lt;a href="https://deevnet.github.io/deevnet-provisioning-api/deevnet-provisioning-api/docs/reference/">API Reference&lt;/a> has every field; this page is what the fields do not say.&lt;/p>
&lt;h2 id="the-key">
 The key
 &lt;a class="anchor" href="#the-key">#&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>The private key never reaches the API.&lt;/strong> A tenant makes it, keeps it, and sends a signing request
made with it. The certificate comes back; the key was never sent.&lt;/p></description></item><item><title>Conventions</title><link>https://deevnet.github.io/deevnet-provisioning-api/docs/conventions/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://deevnet.github.io/deevnet-provisioning-api/docs/conventions/</guid><description>&lt;h1 id="conventions">
 Conventions
 &lt;a class="anchor" href="#conventions">#&lt;/a>
&lt;/h1>
&lt;p>&lt;strong>Every route follows the same few rules.&lt;/strong> They are what make a failed or repeated call safe.&lt;/p>
&lt;h2 id="ensures">
 Ensures
 &lt;a class="anchor" href="#ensures">#&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>Every create is an ensure.&lt;/strong> Calling it again for the same name converges on the same object:
the identity stays, an issued secret is kept, and only what the caller may change is taken. An
object that already exists on a backend is adopted and corrected.&lt;/p>
&lt;p>This is what makes a retry safe, and it is why a tenant&amp;rsquo;s Terraform can re-apply after any
failure.&lt;/p></description></item><item><title>Configuration</title><link>https://deevnet.github.io/deevnet-provisioning-api/docs/configuration/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://deevnet.github.io/deevnet-provisioning-api/docs/configuration/</guid><description>&lt;h1 id="configuration">
 Configuration
 &lt;a class="anchor" href="#configuration">#&lt;/a>
&lt;/h1>
&lt;p>&lt;strong>The service is configured by environment variables only.&lt;/strong> The container has no configuration
file. Values below are illustrative; a site&amp;rsquo;s own come from its automation.&lt;/p>
&lt;h2 id="core">
 Core
 &lt;a class="anchor" href="#core">#&lt;/a>
&lt;/h2>
&lt;p>&lt;strong>Two variables are required, and the service refuses to start without them.&lt;/strong>&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>Variable&lt;/th>
 &lt;th>Required&lt;/th>
 &lt;th>Meaning&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>&lt;code>DEEVNET_API_TOKEN&lt;/code>&lt;/td>
 &lt;td>yes&lt;/td>
 &lt;td>the operator bearer token for &lt;code>/v1&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>DATABASE_URL&lt;/code>&lt;/td>
 &lt;td>yes&lt;/td>
 &lt;td>PostgreSQL connection string. The schema is migrated on start&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>DEEVNET_AGENT_TOKEN&lt;/code>&lt;/td>
 &lt;td>no&lt;/td>
 &lt;td>the exit node&amp;rsquo;s egress agent. It reads &lt;code>GET /v1/fabric/egress&lt;/code> and nothing else&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>DEEVNET_API_LISTEN&lt;/code>&lt;/td>
 &lt;td>no&lt;/td>
 &lt;td>listen address, default &lt;code>:8080&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>&lt;code>DEEVNET_API_TLS_CERT&lt;/code>, &lt;code>DEEVNET_API_TLS_KEY&lt;/code>&lt;/td>
 &lt;td>no&lt;/td>
 &lt;td>serve TLS, 1.2 at the least. Set both or neither&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;p>With only these set, the service runs and every tenant route answers &lt;code>501&lt;/code>.&lt;/p></description></item><item><title>Release Notes</title><link>https://deevnet.github.io/deevnet-provisioning-api/docs/release-notes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://deevnet.github.io/deevnet-provisioning-api/docs/release-notes/</guid><description>&lt;h1 id="release-notes">
 Release Notes
 &lt;a class="anchor" href="#release-notes">#&lt;/a>
&lt;/h1>
&lt;p>Every release of the Deevnet API, newest first. Versions are &lt;code>MAJOR.MINOR.PATCH&lt;/code>; until 1.0 a
minor release may change behavior or configuration, and the notes say so under &lt;strong>Upgrading&lt;/strong>.&lt;/p>
&lt;p>One tag covers everything this repository builds: the API, the two writers
(&lt;code>deevnet-broker-account&lt;/code>, &lt;code>deevnet-log-user&lt;/code>) and &lt;code>deevnet-kit&lt;/code>.&lt;/p>
&lt;h2 id="0120-2026-10-18">
 0.12.0 (2026-10-18)
 &lt;a class="anchor" href="#0120-2026-10-18">#&lt;/a>
&lt;/h2>
&lt;h3 id="added">
 Added
 &lt;a class="anchor" href="#added">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Extra names and addresses on a Pi&amp;rsquo;s server certificate&lt;/strong> (&lt;code>deevnet-kit&lt;/code>, CHG-0050). A Pi
with names in a site&amp;rsquo;s DNS, or about to move to a network where it will have another address,
can have its certificate carry them first. &lt;code>names=&lt;/code> in &lt;code>deevnet-kit.txt&lt;/code> sets them on first
boot; &lt;code>deevnet-kit names [list | set | add | rm]&lt;/code> changes them later, and &lt;code>regen-certs&lt;/code>
applies them. They are kept in &lt;code>/etc/deevnet-kit/names&lt;/code>, one a line. Wildcards are refused.&lt;/li>
&lt;/ul>
&lt;h3 id="changed">
 Changed
 &lt;a class="anchor" href="#changed">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;code>deevnet-kit regen-certs&lt;/code> restarts Grafana as well as the broker and vmauth. Grafana presents
the same certificate on 3000 and kept the old one until it was restarted.&lt;/li>
&lt;/ul>
&lt;h3 id="upgrading">
 Upgrading
 &lt;a class="anchor" href="#upgrading">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>Nothing changes for a card that sets no names. A card initialized on 0.11.0 or earlier can be
given names with &lt;code>deevnet-kit names set&lt;/code> and &lt;code>regen-certs&lt;/code>; no reflash.&lt;/li>
&lt;/ul>
&lt;h2 id="0110-2026-10-11">
 0.11.0 (2026-10-11)
 &lt;a class="anchor" href="#0110-2026-10-11">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-1">
 Added
 &lt;a class="anchor" href="#added-1">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Server certificates for tenants&amp;rsquo; own services&lt;/strong> (ADR-0037, CHG-0047):
&lt;code>PUT&lt;/code>, &lt;code>GET&lt;/code> and &lt;code>DELETE /v1/tenants/{name}/certificates/{certificate}&lt;/code>, and &lt;code>GET&lt;/code> of the list.
A tenant sends a signing request for a label in its own zone; the site&amp;rsquo;s Tenant Service CA, in
OpenBao, signs a one-year server certificate, and it comes back with the chain a server
presents. The request is read for its public key alone. Asking again returns the same
certificate until sixty days before it expires. Nothing is revoked, and nothing renews by
itself.&lt;/li>
&lt;li>A documentation site, 
 &lt;a href="https://deevnet.github.io/deevnet-provisioning-api/">https://deevnet.github.io/deevnet-provisioning-api/&lt;/a>, with the API
reference rendered from &lt;code>api/openapi.yaml&lt;/code>.&lt;/li>
&lt;li>&lt;code>api/openapi.yaml&lt;/code>, an OpenAPI 3.1 specification, as the contract. It replaces
&lt;code>docs/api-v1.md&lt;/code>, and tests fail when it and the code disagree on a route or a wire field.&lt;/li>
&lt;/ul>
&lt;h3 id="upgrading-1">
 Upgrading
 &lt;a class="anchor" href="#upgrading-1">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>Certificates are off until &lt;code>OPENBAO_TENANT_SERVICE_MOUNT&lt;/code> names the Tenant Service CA&amp;rsquo;s mount.
A site without one needs to change nothing. A site with one adds the variable, and &lt;code>update&lt;/code> on
&lt;code>&amp;lt;mount&amp;gt;/sign/tenant-service&lt;/code> to the service&amp;rsquo;s OpenBao policy.&lt;/li>
&lt;li>The database gains a table, &lt;code>tenant_certificates&lt;/code>, by migration &lt;code>0010&lt;/code>. It runs at start.&lt;/li>
&lt;/ul>
&lt;h2 id="0100-2026-10-06">
 0.10.0 (2026-10-06)
 &lt;a class="anchor" href="#0100-2026-10-06">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-2">
 Added
 &lt;a class="anchor" href="#added-2">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Fixed addresses for tenants&amp;rsquo; devices&lt;/strong> (ADR-0035, CHG-0044):
&lt;code>POST&lt;/code>, &lt;code>GET&lt;/code> and &lt;code>DELETE /v1/tenants/{name}/devices/{device}/address&lt;/code>. The API allocates an
address from the range a site sets aside, reserves it on the DHCP server for the device&amp;rsquo;s MAC,
and publishes the device&amp;rsquo;s name in the tenant&amp;rsquo;s zone. A reconcile puts reservations back.&lt;/li>
&lt;li>&lt;code>deevnet-kit&lt;/code> exports the Pi&amp;rsquo;s certificate authority as &lt;code>deevnet-kit-ca.pem&lt;/code> (CHG-0031).&lt;/li>
&lt;/ul>
&lt;h3 id="changed-1">
 Changed
 &lt;a class="anchor" href="#changed-1">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Backend TLS is verified by default.&lt;/strong> &lt;code>PROXMOX_INSECURE_TLS&lt;/code>, &lt;code>OPNSENSE_INSECURE_TLS&lt;/code> and
&lt;code>OMADA_INSECURE_TLS&lt;/code> now default to &lt;code>false&lt;/code>.&lt;/li>
&lt;/ul>
&lt;h3 id="upgrading-2">
 Upgrading
 &lt;a class="anchor" href="#upgrading-2">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>A backend still serving a self-signed certificate must be named.&lt;/strong> Set its
&lt;code>*_INSECURE_TLS&lt;/code> variable to &lt;code>true&lt;/code>, or the API fails to reach it.&lt;/li>
&lt;li>&lt;strong>Fixed addresses are off until configured.&lt;/strong> Set &lt;code>DEEVNET_IOT_ADDRESS_RANGES&lt;/code> to turn them on.&lt;/li>
&lt;/ul>
&lt;h2 id="091-2026-09-27">
 0.9.1 (2026-09-27)
 &lt;a class="anchor" href="#091-2026-09-27">#&lt;/a>
&lt;/h2>
&lt;h3 id="changed-2">
 Changed
 &lt;a class="anchor" href="#changed-2">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>A workload no longer runs a package upgrade on first boot, so it is reachable sooner and its
packages are what the template carried.&lt;/li>
&lt;/ul>
&lt;h2 id="090-2026-09-27">
 0.9.0 (2026-09-27)
 &lt;a class="anchor" href="#090-2026-09-27">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-3">
 Added
 &lt;a class="anchor" href="#added-3">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>A tenant&amp;rsquo;s &lt;code>ssh_keys&lt;/code> reach its workloads&lt;/strong>, and the workload reports the account they
landed on as &lt;code>login_user&lt;/code> (CHG-0028).&lt;/li>
&lt;li>&lt;strong>Admission issues a Wi-Fi key for the tenant developer network&lt;/strong> (CHG-0029), in the trust
class &lt;code>DEEVNET_ADMISSION_WIFI_CLASS&lt;/code> names. &lt;code>DELETE /v1/admissions/{name}&lt;/code> revokes the key of
an admission never used.&lt;/li>
&lt;li>&lt;strong>A Wi-Fi key may bind to one client&lt;/strong> with &lt;code>mac&lt;/code> (CHG-0029).&lt;/li>
&lt;li>The state store&amp;rsquo;s admin client can use TLS, verified against the site&amp;rsquo;s certificate authority:
&lt;code>MINIO_ADMIN_TLS&lt;/code> and &lt;code>MINIO_ADMIN_CACERT&lt;/code> (CHG-0030).&lt;/li>
&lt;li>&lt;code>deevnet-kit&lt;/code> writes the app&amp;rsquo;s broker login to &lt;code>kit.env&lt;/code>, and sets the Pi&amp;rsquo;s host name and
Wi-Fi at first boot.&lt;/li>
&lt;/ul>
&lt;h3 id="changed-3">
 Changed
 &lt;a class="anchor" href="#changed-3">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Deleting a tenant removes its broker accounts&lt;/strong> with it (CHG-0028).&lt;/li>
&lt;/ul>
&lt;h2 id="081-2026-09-24">
 0.8.1 (2026-09-24)
 &lt;a class="anchor" href="#081-2026-09-24">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-4">
 Added
 &lt;a class="anchor" href="#added-4">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;code>deevnet-kit selftest&lt;/code>, and a starter dashboard on the take-home Pi.&lt;/li>
&lt;/ul>
&lt;h2 id="080-2026-09-24">
 0.8.0 (2026-09-24)
 &lt;a class="anchor" href="#080-2026-09-24">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-5">
 Added
 &lt;a class="anchor" href="#added-5">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>A dashboard organization per tenant&lt;/strong> (ADR-0024, CHG-0024). Creating a tenant creates its
organization, an Editor login and three log data sources with fixed UIDs, and returns the
login in &lt;code>dashboard&lt;/code>. The password is also returned on a reconcile.&lt;/li>
&lt;li>&lt;strong>&lt;code>deevnet-kit&lt;/code>&lt;/strong>, the API&amp;rsquo;s stand-in on a take-home Raspberry Pi.&lt;/li>
&lt;/ul>
&lt;h2 id="070-2026-09-22">
 0.7.0 (2026-09-22)
 &lt;a class="anchor" href="#070-2026-09-22">#&lt;/a>
&lt;/h2>
&lt;h3 id="changed-4">
 Changed
 &lt;a class="anchor" href="#changed-4">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>The &lt;code>log&lt;/code> topic level is reserved for devices&amp;rsquo; own logs&lt;/strong> (ADR-0027 §3). A device account
may publish only &lt;code>log/&amp;lt;its own device&amp;gt;&lt;/code>; a workload account may subscribe under &lt;code>log/&lt;/code> and
not publish there.&lt;/li>
&lt;/ul>
&lt;h3 id="upgrading-3">
 Upgrading
 &lt;a class="anchor" href="#upgrading-3">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>A broker account whose patterns reach into &lt;code>log/&lt;/code> in a way this forbids is refused the next
time it is applied.&lt;/li>
&lt;/ul>
&lt;h2 id="060-2026-09-22">
 0.6.0 (2026-09-22)
 &lt;a class="anchor" href="#060-2026-09-22">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-6">
 Added
 &lt;a class="anchor" href="#added-6">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Log store tokens for each tenant&lt;/strong> (CHG-0020): an ingest token and a read token, returned in
&lt;code>log&lt;/code> on create and on reconcile, with the tenant&amp;rsquo;s partition family in &lt;code>account_id&lt;/code>.&lt;/li>
&lt;li>&lt;code>deevnet-log-user&lt;/code>, the writer that maintains the log store&amp;rsquo;s users.&lt;/li>
&lt;/ul>
&lt;h2 id="051-2026-09-20">
 0.5.1 (2026-09-20)
 &lt;a class="anchor" href="#051-2026-09-20">#&lt;/a>
&lt;/h2>
&lt;h3 id="fixed">
 Fixed
 &lt;a class="anchor" href="#fixed">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>The broker account writer&amp;rsquo;s connection pins the host key&amp;rsquo;s algorithm as well as the key, so
it no longer fails against a host that offers several.&lt;/li>
&lt;/ul>
&lt;h2 id="050-2026-09-20">
 0.5.0 (2026-09-20)
 &lt;a class="anchor" href="#050-2026-09-20">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-7">
 Added
 &lt;a class="anchor" href="#added-7">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Broker accounts&lt;/strong> (CHG-0016): &lt;code>POST&lt;/code>, &lt;code>GET&lt;/code> and &lt;code>DELETE /v1/tenants/{name}/broker-accounts&lt;/code>.
The API issues a tenant&amp;rsquo;s MQTT accounts, writes the tenant prefix onto every topic pattern,
and keeps only a hash of the password.&lt;/li>
&lt;li>&lt;code>deevnet-broker-account&lt;/code>, the writer that puts an account into the broker&amp;rsquo;s database.&lt;/li>
&lt;/ul>
&lt;h2 id="040-2026-09-20">
 0.4.0 (2026-09-20)
 &lt;a class="anchor" href="#040-2026-09-20">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-8">
 Added
 &lt;a class="anchor" href="#added-8">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>The device registry&lt;/strong>: &lt;code>POST&lt;/code>, &lt;code>GET&lt;/code> and &lt;code>DELETE /v1/tenants/{name}/devices&lt;/code>. An entry is a
device&amp;rsquo;s identity and grants it nothing.&lt;/li>
&lt;/ul>
&lt;h2 id="031-2026-09-18">
 0.3.1 (2026-09-18)
 &lt;a class="anchor" href="#031-2026-09-18">#&lt;/a>
&lt;/h2>
&lt;h3 id="fixed-1">
 Fixed
 &lt;a class="anchor" href="#fixed-1">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>Revoking a tenant&amp;rsquo;s last Wi-Fi key no longer fails. The wireless controller refuses an empty
key profile, so the API leaves one unusable placeholder entry in it.&lt;/li>
&lt;/ul>
&lt;h2 id="030-2026-09-18">
 0.3.0 (2026-09-18)
 &lt;a class="anchor" href="#030-2026-09-18">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-9">
 Added
 &lt;a class="anchor" href="#added-9">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Wi-Fi keys&lt;/strong>: &lt;code>POST&lt;/code>, &lt;code>GET&lt;/code> and &lt;code>DELETE /v1/tenants/{name}/wifi-keys&lt;/code>. One key per tenant
per trust class; the API chooses the password and takes the SSID and VLAN from the class.&lt;/li>
&lt;/ul>
&lt;h2 id="026-2026-09-17">
 0.2.6 (2026-09-17)
 &lt;a class="anchor" href="#026-2026-09-17">#&lt;/a>
&lt;/h2>
&lt;h3 id="changed-5">
 Changed
 &lt;a class="anchor" href="#changed-5">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>A workload is given a recursive resolver, not the authoritative tenant DNS server.&lt;/li>
&lt;/ul>
&lt;h3 id="upgrading-4">
 Upgrading
 &lt;a class="anchor" href="#upgrading-4">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>&lt;code>DEEVNET_WORKLOAD_RESOLVER&lt;/code> is a new required site variable.&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h2 id="025-2026-09-17">
 0.2.5 (2026-09-17)
 &lt;a class="anchor" href="#025-2026-09-17">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-10">
 Added
 &lt;a class="anchor" href="#added-10">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;code>secrets_stored&lt;/code> on a tenant read: whether the API can still read the secrets it holds.&lt;/li>
&lt;/ul>
&lt;h2 id="024-2026-09-17">
 0.2.4 (2026-09-17)
 &lt;a class="anchor" href="#024-2026-09-17">#&lt;/a>
&lt;/h2>
&lt;h3 id="fixed-2">
 Fixed
 &lt;a class="anchor" href="#fixed-2">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>A stored secret that can no longer be opened loses that secret, not the whole tenant: the
tenant still reads, and supplies the secret again.&lt;/li>
&lt;/ul>
&lt;h2 id="023-2026-09-17">
 0.2.3 (2026-09-17)
 &lt;a class="anchor" href="#023-2026-09-17">#&lt;/a>
&lt;/h2>
&lt;h3 id="changed-6">
 Changed
 &lt;a class="anchor" href="#changed-6">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>The audit log names the caller, and changes to published names are audited.&lt;/li>
&lt;/ul>
&lt;h2 id="022-2026-09-17">
 0.2.2 (2026-09-17)
 &lt;a class="anchor" href="#022-2026-09-17">#&lt;/a>
&lt;/h2>
&lt;h3 id="fixed-3">
 Fixed
 &lt;a class="anchor" href="#fixed-3">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>A tenant whose registry row is gone is answered &lt;code>404&lt;/code>, not &lt;code>401&lt;/code>, so its provider restores it.&lt;/li>
&lt;/ul>
&lt;h2 id="021-2026-09-17">
 0.2.1 (2026-09-17)
 &lt;a class="anchor" href="#021-2026-09-17">#&lt;/a>
&lt;/h2>
&lt;h3 id="fixed-4">
 Fixed
 &lt;a class="anchor" href="#fixed-4">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>A name published beside a workload no longer takes over the workload&amp;rsquo;s reverse record.&lt;/li>
&lt;/ul>
&lt;h2 id="020-2026-09-17">
 0.2.0 (2026-09-17)
 &lt;a class="anchor" href="#020-2026-09-17">#&lt;/a>
&lt;/h2>
&lt;h3 id="added-11">
 Added
 &lt;a class="anchor" href="#added-11">#&lt;/a>
&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Tenants&lt;/strong>: create, restore, resume, read, reconcile and delete, with index allocation
against the registry and the fabric (ADR-0015).&lt;/li>
&lt;li>&lt;strong>Admission and tenant tokens&lt;/strong>: &lt;code>POST /v1/admissions&lt;/code> issues a single-use enrollment token,
and a tenant&amp;rsquo;s own token verifies without the registry (ADR-0015 §10).&lt;/li>
&lt;li>&lt;strong>Workloads and published names&lt;/strong>: &lt;code>/v1/tenants/{name}/workloads&lt;/code> and &lt;code>/records&lt;/code>
(ADR-0015 §11–§13).&lt;/li>
&lt;li>&lt;strong>Secrets from OpenBao&lt;/strong>, sealing of stored tenant secrets, and TLS (ADR-0016).&lt;/li>
&lt;li>&lt;strong>An agent token&lt;/strong> that reads &lt;code>GET /v1/fabric/egress&lt;/code> and nothing else (ADR-0015 §7).&lt;/li>
&lt;/ul>
&lt;h2 id="010-2026-09-14">
 0.1.0 (2026-09-14)
 &lt;a class="anchor" href="#010-2026-09-14">#&lt;/a>
&lt;/h2>
&lt;p>The first tagged release.&lt;/p></description></item></channel></rss>