Skip to content

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.

  • Design certainty (“is this decided?”) lives inline on each page as Open question asides. 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.

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.

BadgeMeaning
DesignThe model is specified; little or none of it is coded yet.
PartialSome capabilities on the page are built and tested; others are still Design.
BuiltEvery capability the page describes is implemented and tested.
DivergedBuilt, 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.

CapabilityStatus
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

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 migrate applies an embedded dbmate migration (pure DDL, run-once, idempotent) against a BYO Postgres, and omniglass server serves GET /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 (an owner@all grant 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 :read floor) into a capability set. Two seams every later route reuses: the capability fast-reject (401 unauthenticated, 403 missing capability) and GET /api/v1/auth/me (principal, permissions, grants). GET /api/v1/roles is gated by role: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 resolves owner@all to all (the per-action visible_set resolver 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_type registry (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); location is a name-addressable, variable-depth tree (parent_id) whose type is a foreign key. POST/GET/PATCH/DELETE/list /api/v1/locations each declare a location:<action> capability and resolve the caller’s per-action visible_set from 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 the audit_log row 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 are all and location only; the location_type registry seeds official rows only; rank orders and signals hierarchy but does not constrain nesting; update patches display_name and location_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 any LocationTemplate.
  • Slice 3a: the generated CLI. The second stage of the generation pipeline lands: make gen now runs cmd/cligen, which reads the committed api/openapi.json and 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 --help plus the example come from the operation’s summary and description. A :verb custom 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 (the server/migrate run modes and the trusted bootstrap lane) on one root through a stable seam (internal/cli/api_hooks.go: the JSON-over-HTTP client and the shared --server/--token flags), 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 gen now also emits the typed SPA client (openapi-typescript to web/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 bare go build/go test needs no dist/ and serves a build-the-console placeholder, while make build-web runs the Vite build and compiles with -tags web to embed the real shell. The visual system is the “Omniglass Console” design ported faithfully (the theme.css tokens 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 by internal/webui unit tests (the SPA-fallback routing against a fake FS, and the real embed under -tags web), an API mount test (/web serves the shell and the bare /web redirects), 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.