Configuration & GitOps
Configuration & GitOps
Section titled “Configuration & GitOps”The configuration of a Phrame system is a git repository. Everything downstream is distribution.
- Source of truth: the
phrame-configrepository in GitLab - Bridge:
services/config-sync(Node / TypeScript daemon) - Distribution: retained MQTT topics,
qos: 1
config-sync
Section titled “config-sync”GitLab is the source of truth; MQTT is the distribution channel; config-sync
is the bridge between them.
- On startup it authenticates to the broker, loads the JSON schemas from the
config repository, then performs a full sync — reading every file under
configs/and publishing each to its retained topic. - On every git push a webhook fires, and it re-syncs only the added or modified files, publishing an audit event describing the commit.
- Every file is validated against its JSON schema before publishing. A file that fails validation is logged and skipped — it is never published. A bad commit therefore cannot take the system down; it simply fails to take effect, leaving the last good configuration retained on the broker.
Schemas live in configs/schemas/*.json and are registered under the filename —
node-config.json becomes schema id node-config. If schema loading fails at
startup, validation is disabled with a warning and syncing continues, on the
argument that distributing unvalidated configuration beats distributing none.
Only paths matching a known pattern are published. Schemas, Lua scripts and
registry.json are deliberately ignored rather than pushed to the broker.
The live ref is a runtime value
Section titled “The live ref is a runtime value”The git ref is not fixed at deploy time. The retained topic
{prefix}/system/gitlab/ref, with payload {"ref":"<branch|tag|sha>"}, is the
runtime source of truth:
mosquitto_pub -t phrame/system/gitlab/ref -r -m '{"ref":"v1.4.0"}'- At startup,
config-syncadopts the retained value if the topic is set, deliberately ignoring theGITLAB_REFenvironment variable as potentially stale. If the topic is empty, it seeds it fromGITLAB_REF. So the environment variable is only ever a cold-start seed. - At runtime, publishing a new ref re-evaluates lock state and runs a full re-sync from the new ref. Changes are serialised, and an unchanged value is a no-op.
Branch versus tag is a real behavioural difference. A branch ref keeps the git-push webhook live, so commits republish changed files. A tag or commit SHA locks it: pushes are ignored, and the reconciler refuses to write. The only way to move a pinned deployment is to publish a new ref here.
That is what lets “this facility is running v1.4.0” be a statement about the running system rather than about its inputs.
Operator — choosing the live configuration
Section titled “Operator — choosing the live configuration”Selecting that ref is not a job for mosquitto_pub in production. The
Operator SPA (services/operator) lets an operator choose the active
platform configuration — a GitLab tag or branch of phrame-config — and create
new branches from the UI.
It is deliberately thin. The REST and GitLab gateway lives in config-sync
(GET /gitlab/tags, /branches, /ref; POST /gitlab/ref, /gitlab/branches),
and setting a ref reuses the existing retained-topic control path rather than
introducing a second way to do the same thing. Authentication is a Keycloak JWT
verified against JWKS, requiring the phrame-operator role (phrame-admin is
also accepted).
Editing configuration directly
Section titled “Editing configuration directly”phrame-config (the service) is the versioned config editor for changes not
made through Architect: acquire a Redis lock (30-minute TTL),
edit in Monaco, save — which verifies the lock still holds — commit and push to
GitLab, then release. Locks are broadcast over Socket.io so a second editor sees
the file is held rather than discovering it at save time. State is SQLite for
users, Redis for sessions and distributed file locks.
Shared contracts
Section titled “Shared contracts”@phrame/orchestration-contracts is published to the GitLab npm registry on git
tag, with the tag required to equal the package.json version so the two cannot
drift. It exports the naming rules for every UUID-derived identifier, the
orchestration event payload and its validator, the container-spec type, and the
instance-config types.
Consumers authenticate to the registry with CI_JOB_TOKEN via a repo-root
.npmrc, which means the contracts project’s job-token allowlist must permit
each consumer — a step that is easy to miss and fails the consumer’s build
rather than the contract’s.
