Implementation status
The architecture pages describe the target model in present tense: the design, not a snapshot of the code. This page is the other axis, what is actually built, tracked per capability. The split is deliberate: the architecture stays a stable reference, and build progress lives here instead of rotting the design prose with “shipped in vX” notes.
Two axes, kept separate
Section titled “Two axes, kept separate”- Design certainty (“is this decided?”) lives inline on each page as
Open questionasides. A page can be almost fully settled yet still carry a few open points. - Implementation state (“is this built?”) lives here, and as each page’s status badge. A page can be fully designed and entirely unbuilt, or anywhere in between.
Neither axis is page-binary, and they do not move together.
Badge legend
Section titled “Badge legend”Each architecture page carries one status badge, and the grid below is sourced live from those
badges, so it never drifts. The badge is the page’s floor: a page is only Built once every
capability it describes is shipped, so a page with one unbuilt corner stays Partial.
| Badge | Meaning |
|---|---|
| Design | The model is specified; little or none of it is coded yet. |
| Partial | Some capabilities on the page are built and tested; others are still Design. |
| Built | Every capability the page describes is implemented and tested. |
| Diverged | Built, but the implementation differs from the design on this page. The page carries a note pointing to what shipped and why; this is a proposed architecture, and building it sometimes changes it. |
Today the repository is public and published ahead of the code, so the whole architecture is
Design. As a page’s badge flips to Partial, Built, or Diverged, the grid below follows
automatically.
The grid
Section titled “The grid”| Capability | Status |
|---|---|
| AI | Design |
| Alarms and actions | Design |
| API | Design |
| Audit | Design |
| Calculations | Design |
| Cascade | Design |
| Config, credentials, and variables | Design |
| Core entities | Design |
| Data collection | Design |
| Datapoints | Design |
| Events | Design |
| Expressions | Design |
| Files and blobs | Design |
| Groups | Design |
| Health, KPIs, and service levels | Design |
| Identity and access | Design |
| Messaging | Design |
| Nodes | Design |
| Scaling and deployment | Design |
| Storage | Design |
| Templates | Design |
| Time | Design |
| UI | Design |
| Views | Design |
| Workers | Design |
Build progress
Section titled “Build progress”The code is built one vertical slice per PR, each a thin cut through the whole stack rather than a
horizontal layer. Slices are logged here as they land; a page’s badge only flips once all of its
capabilities ship, so an early slice can prove a seam without moving any page off Design.
- Slice 1: the walking skeleton. The single binary boots in two run modes.
omniglass migrateapplies an embedded dbmate migration (pure DDL, run-once, idempotent) against a BYO Postgres, andomniglass serverservesGET /api/v1/healthz, which pings the database through the Storage Gateway seam (the only DB path, an interface from day one) and reports{status, db}. The HTTP API is Huma over chi; the OpenAPI 3.1 document is generated from the Go structs (make gen), the source the typed clients are generated from in later slices. Proven by a testcontainer integration test (migrate applies clean, the gateway ping succeeds, healthz returns db ok) and an end-to-end test that builds and runs the real binary. Deferred to later slices: messaging (NATS / JetStream), the worker and outbox, identity and access (Slice 2), the collection engine, and the rest of the generation pipeline. - Slice 2: the auth and gateway foundation. The identity and access
schema (principal with per-kind human and service, credential, role, principal_grant, and audit_log;
uuidv7 keys), the four official roles seeded idempotently at boot, and
omniglass bootstrap <username>, which creates the first owner (anowner@allgrant plus a bearer credential) directly and idempotently. A bearer token resolves through the Storage Gateway to its principal and grants, which flatten through the role index (inheritance, wildcards, the:readfloor) into a capability set. Two seams every later route reuses: the capability fast-reject (401 unauthenticated, 403 missing capability) andGET /api/v1/auth/me(principal, permissions, grants).GET /api/v1/rolesis gated byrole:read. Proven by testcontainer integration and end-to-end tests (bootstrap idempotency, the 401 and 403 paths, the auth/me contract) plus pure unit tests of the capability logic. Deliberate thin cuts this slice: bearer-token auth only, and scope resolvesowner@allto all (the per-actionvisible_setresolver lands when there are entities to scope). Deferred: password and OIDC auth, the first-class agent principal, and CDC-driven cache invalidation of the role index. - Slice 3: locations and the per-action scope resolver. The first scoped core entity,
and where the ABAC scope deferred from Slice 2 lands. The
location_typeregistry (the only shape-definer, since a location has no template) seeds the four official types at boot, ranked (campus/building/floor/room, spaced by ten);locationis a name-addressable, variable-depth tree (parent_id) whose type is a foreign key.POST/GET/PATCH/DELETE/list /api/v1/locationseach declare alocation:<action>capability and resolve the caller’s per-actionvisible_setfrom their grants and the role index, which the Storage Gateway expands (a recursive subtree walk) into the row filter and applies on every query, writing theaudit_logrow in the same transaction. The over-permit fix is real: only the grants whose role carries the action contribute scope, so a read-anywhere principal with a narrow write grant cannot write outside it. The three-way status split holds: out of read scope is a non-disclosing 404, readable-but-out-of-action-scope is 403, and a delete is refused (409) while the location still has children. Proven by a pure resolver unit suite, a testcontainer integration test of the gateway split and audit, and an end-to-end HTTP test (scoped subtree listing, the 404, the capability 403, full owner CRUD). Deliberate thin cuts: scope kinds resolved areallandlocationonly; thelocation_typeregistry seeds official rows only;rankorders and signals hierarchy but does not constrain nesting; update patchesdisplay_nameandlocation_type(rename and reparent deferred); occupancy counts child locations only (placed systems and components join when they land). Deferred: system and component entities (Slice 4), group and dynamic-group scope (Slice 5), operator-defined location types via the namespace shadow, hard containment rules, and anyLocationTemplate. - Slice 3a: the generated CLI. The second stage of the generation pipeline
lands:
make gennow runscmd/cligen, which reads the committedapi/openapi.jsonand emits the cobra command tree (internal/cli/api_gen.go, committed and reviewed like the spec). One command per operation, derived from the AIP path and method:omniglass location list/get/create/update/delete,role list,auth me,healthz; path parameters are positional args, the request body becomes--flags, and--helpplus the example come from the operation’s summary and description. A:verbcustom method maps to<resource> <verb> <id>generically, so future custom methods surface with no generator change. The generated tree composes with the hand-written commands (theserver/migraterun modes and the trustedbootstraplane) on one root through a stable seam (internal/cli/api_hooks.go: the JSON-over-HTTP client and the shared--server/--tokenflags), so regeneration never touches a hand-written command. The CLI is a client of the API like any other: it carries a bearer token and the server enforces the same capability and scope. Proven by an end-to-end test that builds the real binary, bootstraps an owner with the hand-written command, and drives the generated location commands against a live server (create, scoped list, get, a non-disclosing 404 as a non-zero exit, delete, and--help). Deliberate thin cuts: JSON output only (no table rendering), and a generic client rather than a per-type typed one. Deferred: the typed SPA client and UI (Slice 3b), shell completion, multi-server contexts, and the YAML JSONSchema target. - Slice 3b: the operator console. The last stage of the generation pipeline
and the first browser surface: a SolidJS SPA (Vite, daisyUI 5 on Tailwind 4,
@solidjs/router,@tanstack/solid-query,openapi-fetch)go:embed’d into the binary and served under/web.make gennow also emits the typed SPA client (openapi-typescripttoweb/src/api/schema.gen.ts), so the console cannot drift from the API. The embed uses the build-tag-with-fallback pattern (internal/webui): a barego build/go testneeds nodist/and serves a build-the-console placeholder, whilemake build-webruns the Vite build and compiles with-tags webto embed the real shell. The visual system is the “Omniglass Console” design ported faithfully (thetheme.csstokens and component classes, the nav rail, top bar, density/theme tweaks, and Home). Locations is the first live view: list, detail, and create through the typed client, gated by a bearer-token login and the AuthGuard; the other nav sections render honest stubs until their backends land. Proven byinternal/webuiunit tests (the SPA-fallback routing against a fake FS, and the real embed under-tags web), an API mount test (/webserves the shell and the bare/webredirects), and a vitest suite over the locations data layer. Deliberate thin cuts: only locations is wired live; styling is daisyUI (two brand themes via the plugin) with Kobalte deferred until the first interactive widget needs it; auth is bearer-token paste, not a full login. Deferred: the mock-data screens (alarms, systems, components, dashboards) as their backends land, HTTP/2 (h2c) and the SSE live relay, and full auth UX.