From Build to Bench
A station may only flash what the published catalog authorizes for its batch. Publishing a release on GitHub makes it available. A separate catalog publication authorizes its use for batches. Neither action enables fleet OTA updates.
Build & review draft → Publish on GitHub → Register & verify → Authorize for batches → Station installation.
Published assets and release notes live in Omatico-Firmware-Releases ↗. The admin Releases page shows stored verification and current batch selections, with GitHub discovery and notes available on request.
The objects
Six things participate in getting firmware onto a device. Three are created ahead of time (batch, spec, station credential), one is created per release (the release record), and two are never created directly at all — the catalog is computed, and the published version is a receipt.
Firmware release record
One row per firmware version, registered by exact version from the public release repository and verified
independently from the delivered bytes: the four assets (descriptor, factory payload, MicroPython runtime, OTA
archive), their SHA-256 digests, the source commit and CI run, and the baselineBundle the backend
derived itself.
Created by register-release --version vX.Y.Z. Nobody types a filename or a digest; the
backend downloads, re-hashes, inspects, and derives.
Why — every digest the catalog pins traces to bytes the backend verified. Rows are append-only, unique by version, and never edited; a corrected build is a new version.
Build spec
One authorized factory configuration, under a code like FBS-MAP-2-IEL-C-002: board codes, firmware
version, pinned content digests, test profile, and a reference to the firmware release record it was minted
from. It is the unit the catalog authorizes and the record every provision request is validated against.
Created by an admin when a configuration is first authorized — and minted automatically by every publish thereafter.
Why — specs are immutable pins. A new release never edits a spec; it mints a successor and retires the old one once no batch references it, so what was authorized when stays answerable.
Batch
A manufacturing authorization: which production run may build devices, how many (quota), and which specs it may
use. Identified like MFG-20260701-MAP-C-001.
Created by an Omatico admin before any device work starts. Quota counts unique devices — reflashing a known device never re-consumes a slot.
Why — nothing flashes without an authorization envelope and a ceiling. Suspending a batch is the immediate brake on a line.
Catalog
Not a stored document. The catalog is the answer to GET /v1/catalog, computed on every read from
the open batches and their active specs, plus the asset-digest allowlist and an expiry.
Created — never. It is derived, so the backend has no separately stored catalog to invalidate. Stations still need to sync and respect its expiry.
Why — what a station sees is, by construction, exactly what the database authorizes. The catalog cannot drift from the tables it is computed from.
Published catalog version
"Published" is not a toggle and there is no draft state. A version (e.g. 2026-08-26.1) is stamped
on batches by a publish; the served version is the highest among open batches. The publication row is the
immutable receipt of that publish: who, why, prior → resulting map, payload hash.
Created inside the publish transaction itself — one row per publish, and each version string can be claimed exactly once.
Why — the receipt commits atomically with the change it describes, so the audit trail can never disagree with the catalog.
Where — every receipt is browsable, read-only, in the admin console under Releases →
Publication history (/admin/provisioning/releases/publications): who, why, the prior →
resulting map per batch, the payload hash, and the stored pre-commit result.
Station
A bench identity, not a database object of its own: a machine credential bound to a factory and station ID, allow-listed to specific batches, with an expiry that forces re-enrollment.
Created dormant by an admin and activated when the station redeems its enrollment code.
Why — the catalog is scoped per credential. A station is only ever shown the batches it may later provision against.
The route: build → catalog → station
The batch, its spec, and the station credential already exist before a release moves (they are the standing authorization). A release then travels the numbered path below. Callouts ① – ⑧ are described in the legend.
- Build and review a draft, then publish on GitHub — CI builds the descriptor, factory payload, exact MicroPython runtime and application archive. Review those four assets and replace any fixture title or notes before approving publication. The publication workflow verifies and publishes the existing draft in place; it does not rebuild. Published assets and the tag are permanent, while the title and notes remain editable. Immutability is GitHub's guarantee that the assets can never be swapped after publication.
-
The backend registers the release by exact version — it resolves the tag anonymously, refuses a draft
or a mutable release, downloads the four assets, re-hashes them, inspects the payload, and derives
baselineBundleitself from the application,/lib, andproduct_configdigests it recomputed. A transcribed digest cannot reach the catalog: the backend derives the value and refuses a release whose own claim disagrees. -
An admin selects the release by version —
publish-release --firmware-versionnames the registered record, source spec, affected batches, a new catalog version, reason and operator. Filenames and digests are derived automatically. One release model, one registration path, one way to publish. -
Publish runs as one transaction — mint the successor spec (firmware version and baseline copied from
the release record, which the spec references), relink the affected batches, retire the old spec if no batch
references it, stamp the new
catalogVersion, write the publication receipt. An invariant gate runs before commit; any failure rolls the whole thing back. A half-applied catalog would be unsafe, so it cannot exist. -
/v1/catalognow serves the new pins — recomputed on read, so there is no cache to invalidate; a post-commit confirmation verifies the served state. Pre-commit checks the intended catalog; post-commit checks the one actually served. - The station syncs — its credential scopes the response to the batches on its allow-list, each carrying its allowed configurations and their digests.
- The station verifies, then flashes — it re-computes the digests of the bytes it staged and proceeds only when the staged baseline matches the catalog and the assets match the release descriptor. The runtime digest comes from the release assets, not the catalog response.
-
The station provisions the device — reporting batch, spec code,
catalogVersion, and its computed asset digests. The backend re-checks everything (Table 1) against the release record the spec references, reserves one quota slot, creates the device identity, and writes the device shadow'sdesired.appfrom the validated spec — the catalog-authorized firmware, resolved fresh and failing closed if it can't be.
What one publish changes
Catalog publication preserves release records and existing spec pins. It mints a successor spec that references the selected release record, moves the batch's authorization link, and retires what is no longer referenced — the old spec and every release record remain, inert, for provenance. That is why a "published catalog version" is a receipt rather than a flag: the version exists because this transaction happened.
active → retired).
Release records are never changed. Rolling back is simply another publish that selects the earlier registered
release under a fresh version — history is never rewritten.
The handshake: who is the authority
The catalog names the authorized firmware version and baseline. The station fetches that exact release, checks its descriptor and assets, computes the staged baseline and runtime digests, and reports them when provisioning. The backend compares those reported digests with its independently verified release record. Equality validates the reported values; it does not independently prove that the device was flashed. Station flash and device checks provide that evidence.
The backend authorizes
The catalog supplies the batch, configuration, firmware version and expected baseline.
The runtime digest stays in the registered release record.
The station verifies
The station checks the exact release assets and computes the staged baseline and runtime digests before installation.
It reports those computed digests when provisioning.
Provision request → backend: compare the reported digests with the verified release record. Reject any mismatch.
configCatalog field is a compatibility echo of the spec's pin, not a hash of the served catalog
bytes.
| Check | A failure means | |
|---|---|---|
| 1 | Batch is open, with quota remaining | A closed or suspended batch stops the line instantly — suspension is the emergency brake. |
| 2 | Spec is active and authorized by that batch | Retired specs and specs from other batches are refused, even if the station still has them cached. |
| 3 | catalogVersion equals the batch's version | A station on a stale sync must re-sync before it may provision. |
| 4 | Observed board codes match the spec | The physical hardware on the bench must be what the configuration says it is. |
| 5 | The spec references a registered firmware release | A configuration whose bytes the backend never verified authorizes nothing. |
| 6 | baselineBundle digest equals the release's derived baseline | The reported baseline differs from the authorized application, /lib, and
product_config baseline. |
| 7 | firmwareImage digest equals the release's MicroPython runtime | The reported runtime digest differs from the runtime verified at registration. |
| 8 | configCatalog digest equals the spec's pin | The echoed compatibility pin differs from the spec. This field does not verify catalog content bytes. |
The station-computed toolBinary digest is recorded for audit but is not yet grounds for rejection.
Design rules the flow enforces
Immutable pins
A spec is never edited. Every catalog publication mints a successor and retires the predecessor when no batch references it, so "what was authorized on that date" is always answerable.
One transaction
Spec mint, batch relink, retirement, version stamp, and the publication receipt commit together or not at all. There is no partially published catalog.
Computed on each read
The catalog is computed from the tables on every read. It cannot drift from the database, because it is the database.
Authority inversion
The backend authorizes the release; the station verifies the bytes and reports computed digests. The backend rejects values that disagree with its verified release record.
Rollback is a publish
Rolling back selects an earlier registered release under a fresh version — never by rewriting history. Suspending the batch is the immediate stop.
Derived, never transcribed
baselineBundle is derived by the backend from bytes it re-hashed and compared with the release's
own claim. No operator ever types a filename or a digest on the release path.
Terminology follows the admin release & catalog concepts guide. Spec codes, batch IDs, versions, and publication ids shown in the figures are representative examples.