Feature Specification: Configuration model and JSON Schema
Created: 2026-09-22
Status: Implemented
Input: Reconstructed from the configuration type and the schema library of core at 0bf4ecd36, pull requests #1219, #1289, #1839 and #1843, commit c58cd36b7, and api/specification.md.
User Scenarios & Testing (mandatory)
User Story 1 - Render the operator form from a schema (Priority: P1)
A front end shows a form for the configuration of a deployment. It gets the JSON Schema of that configuration from core, and renders the form from the schema alone. It does not know the names of the applications or of their options. A hosting provider gets a provider schema: the same schema without the fields that its hosting configuration fixes, and with only the deployment method that the provider pins (#723). core also cuts the provider schema into one schema per application, so a consumer can validate one application without the full schema.
Why this priority: the schema is the contract between core and every front end. Without it, a front end must copy knowledge of the model.
Independent Test: build the operator schema. It is a JSON Schema of draft 2020-12 with one property per operator-facing application.
Acceptance Scenarios:
- Given the default groups, When the schema is built, Then it is valid draft 2020-12 JSON Schema and has a property for each operator-facing application.
- Given a hosting configuration that fixes a value, When the provider schema is built, Then the fixed field is not in the schema.
- Given a provider that pins one deployment method, When the provider schema is built, Then the schema offers only that method and no choice of method.
- Given a new deployment, When a front end reads the root default of the schema, Then it gets the configuration that the form starts from.
- Given an option with a technical abbreviation in its name (for example CIDR), When the schema is built, Then the title of the field uses the correct form of the abbreviation.
- Given the provider schema, When the per-application schemas are built, Then each one validates a value for its application without the full schema.
User Story 2 - Validate a submission with the type of the deployment (Priority: P1)
A front end validates a submitted configuration with the same type that the deployment uses. It evaluates a configuration type that core builds from the components alone, without the setups.
Independent Test: compare the hash of the configuration type with the hash of the front-end configuration of each deployment method. They are equal.
Acceptance Scenarios:
- Given the components, When the configuration type is evaluated, Then it is equal to the front-end configuration of every deployment method.
User Story 3 - Keep stored configurations valid after a rename (Priority: P2)
An option is renamed. A front end has configurations stored with the old name. core publishes a rename table of old and new option paths, so the front end can migrate the stored values.
Independent Test: rename an option in a test module. The rename table has the entry, and the schema does not show the old name.
User Story 4 - Narrow the schema that an application publishes (Priority: P3)
An application (PeerTube, Mastodon, Pixelfed) publishes a JSON Schema for its own settings. Its component narrows that schema to the settings that it gives to the operator, and offers the result as its settings option. core validates values against that schema in Nix, so a wrong setting fails at evaluation.
Independent Test: a unit test that validates a valid and an invalid value against a small schema. The first passes, the second fails with a named error.
Acceptance Scenarios:
- Given the schema of an application and a selection of its settings, When the schema is narrowed, Then it has only the selected settings, and a value with a setting that is not selected fails.
- Given a selected name that ends in a wildcard (for example
DEEPL_*), When the selection is resolved, Then it selects each setting whose name starts with the part before the wildcard. - Given a selected name or pattern that matches no setting, When the selection is resolved, Then the evaluation fails with a named error.
- Given an application schema whose properties name the environment variable that they set (Pixelfed), When the schema is keyed by environment variable, Then properties that set the same variable become one property, and properties with types that do not agree fail.
Edge Cases
- A schema of another dialect, a reference that is not local, a reference to an anchor, or a reference with sibling keywords: the validator fails with a named error.
- Patterns are ECMA-262, and Nix matches POSIX extended regular expressions. The validator restates in POSIX the ECMA-262 constructs that POSIX spells differently (character class escapes, control escapes, escaped punctuation, lazy quantifiers). A construct with no POSIX equivalent, such as a lookahead or a word boundary, fails with a named error. Lengths count bytes, and formats are not asserted. The test suite records each case of the official suite that fails for these reasons.
- When the build cannot read the hosting configuration and the pin of the provider, the provider schema is the operator schema with no prune and no pin.
- A submission names only some applications. The named applications replace the baked entries, with an explicit on or off. The other applications keep the baked entries.
- A stored configuration from before FR-011 can still hold a secret value in the settings. The next submit through
apimoves the value to KV, as for a new value. Nothing else moves or removes it: the stored builds ofapikeep their snapshot, and the Nix store of a node that ran such a build keeps the value until a garbage collection removes it.
Requirements (mandatory)
Functional Requirements
- FR-001:
coreMUST publish the JSON Schema (draft 2020-12) of the operator configuration, generated from the module options. - FR-002: The provider schema MUST NOT show fields that the hosting configuration fixes, MUST NOT show renamed (alias) options, and MUST offer only the deployment method that the provider pins.
- FR-003: The schema MUST have a root default: the configuration a new deployment starts from. The root default MUST contain only the deployment method and the
enableoptions, eachenableoption at the default that its module declares. A front end MAY use the root default as a value, becausecorewrites it for that purpose. - FR-004: The configuration type MUST be built from the components alone and MUST be equal to the front-end configuration of every deployment method.
- FR-005:
coreMUST publish a rename table of the options, so front ends can migrate stored configurations. - FR-006: The in-Nix validator MUST validate values against a JSON Schema. It MUST NOT convert a schema into Nix options, because keywords such as
patternanddependentRequiredhave no correct equivalent. - FR-007: The submitted configuration MUST merge with the baked configuration per application, as the edge cases describe.
- FR-008: Front ends MUST be able to treat the options as opaque: the schema, its titles and its default are enough to render and submit a form.
- FR-009: The
settingsoption of an application component MUST take its schema from the schema that the application publishes, narrowed to the settings that the component selects for the operator. An audit table MUST record for each setting whether it is for the operator, with the reason. - FR-010:
coreMUST publish the schema of each application, cut from the provider schema, so a consumer does not walk the schema itself. - FR-011: The schema MUST mark each secret that the operator brings to an application as write-only, with the password format and no default. The secrets are the sensitive settings for the operator in the audit table, less their public keys. Each application with such secrets MUST have a read-only option that lists the secrets that have a value in the OpenBao KV of the deployment. The KV instance of the secrets of an application MUST be
<application>-operator-secrets, with one entry for each write-only field. The evaluation MUST fail when a secret value is in the settings, so that no value gets into the Nix store. - FR-012: The
defaultof an option MUST be only a hint to show what the module does when the option has no value. It is the value of the module as it is, except when FR-013 names it as Markdown. It can be invalid for the option. A front end MUST NOT write it into a configuration on its own. An absent key means that the default of the module applies. - FR-013: The schema MUST name each string in
defaultandexamplesthat is Markdown in the annotationx-markdown, as a list of JSON Pointers relative to the schema of the option, thus a client can tell adefaultthat comes from adefaultTextfrom adefaultthat comes from a value. A string that the annotation does not name is a value and is not escaped.
Key Entities
- Operator configuration: the domain, the contact email and the applications of one deployment.
- Schema: the JSON Schema of the operator configuration, with a root default. The provider schema is its pruned and pinned form.
- Operator options audit: one row for each option or setting of an application, with its audience, whether it is only
partially for the operator, whether it issensitive, and notes. - Rename table: a list of old and new option paths.
- Default configuration: the seed that the setups and the tests start from.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: A front end renders the form of all applications with no application names in its code.
- SC-002: The unit tests of the validator, the rename table, the titles, the selection of settings, the merge of a submission and the operator configuration pass.
- SC-003: The validator passes all cases of the official JSON Schema suite, except the cases that the suite test records.
Assumptions
api/docs/design/schema-and-sources.mddescribes howapiserves this schema. This specification describes only whatcoreproduces.- The applications that a new deployment starts with are a decision of the front end (constitution principle II). The default configuration of the setups and the tests turns off every application.
Known Deviations
- A
partialsetting is given to the operator in full, not only the part that is for the operator (#2132).