Replace the settings row's (cluster, subdomain) identity columns with
(domain, proxy_address): domain is the endpoint hostname agents call,
proxy_address the declared cluster address of the proxy serving it. The
serving shape is the shape of the pin -- self-addressed (domain ==
proxy_address, a proxy dedicated to the account) or labeled (domain one
label beneath a shared cluster's address) -- so no mode flag or config
exists anywhere, and any mix of shapes coexists per account on one
deployment.
Bootstrap becomes an explicit POST /api/agent-network/settings taking
exactly one of proxy_address (the server allocates an adjective-noun
label beneath it) or endpoint (claimed verbatim, address-first). The
identity fields leave the PUT schema entirely -- immutability by shape
rather than by runtime rejection -- and provider create loses
bootstrap_cluster and all settings side effects, which also retires two
latent bugs: the dashboard's unsorted [0] free-domain pick making a
permanent decision, and bootstrap failures swallowed at debug level
inside a 200 provider create.
The global unique index moves from the label to the full hostname --
the actual invariant. Labels may repeat across clusters again, and
self-addressed rows (no label) cannot collide on an empty string. The
reverse lookup becomes a point query on that index, deleting the
clusterFromDomain suffix heuristic, and the synthesizer stamps
ProxyCluster from the settings row as a field read. A pre-AutoMigrate
migration backfills existing rows (domain = subdomain.cluster,
proxy_address = cluster) and drops the legacy columns, failing loudly
on rows with no identity to derive.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
## Describe your changes
Work on the Terraform provider (terraform-provider-netbird #177–#183)
surfaced places where the agent-network API broke its own contracts or
deviated from the conventions the rest of the management API follows,
forcing client-side workarounds.
Settings reads now follow the settings-endpoint convention: GET always
answers with a JSON object. Before bootstrap it returns the defaults
with an empty cluster/subdomain/endpoint (previously 200 with a JSON
`null` body, while the spec said 404). The settings PUT can bootstrap
the account by carrying a `cluster` — previously the row could only come
into existence through the first provider create, and a settings-first
setup was impossible; a differing cluster on a bootstrapped account is
rejected instead of silently ignored. PUT remains full-state.
The provider PUT schema promised omit-preserves semantics for several
operator-editable fields that the handler never delivered (it builds the
row from the request, like every other update handler). The schema
wording now matches the shipped full-state behavior; only the api_key
(secret) and session keys stay preserved by the manager. Identity
headers are always present in provider responses so an explicitly
cleared value round-trips as an empty string.
The Go REST client gains the full agent-network surface (catalog,
providers, policies, guardrails, budget rules, settings), including a
shim translating the legacy 200+`null` settings body from older servers
into an `IsNotFound` error.
Note for reviewers: the dashboard special-cased the `null` settings
body; it needs a small follow-up for the new defaults response (in
progress).