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:

  1. 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.
  2. Given a hosting configuration that fixes a value, When the provider schema is built, Then the fixed field is not in the schema.
  3. 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.
  4. 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.
  5. 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.
  6. 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:

  1. 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:

  1. 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.
  2. 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.
  3. Given a selected name or pattern that matches no setting, When the selection is resolved, Then the evaluation fails with a named error.
  4. 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 api moves the value to KV, as for a new value. Nothing else moves or removes it: the stored builds of api keep 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: core MUST 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 enable options, each enable option at the default that its module declares. A front end MAY use the root default as a value, because core writes 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: core MUST 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 pattern and dependentRequired have 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 settings option 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: core MUST 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 default of 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 default and examples that is Markdown in the annotation x-markdown, as a list of JSON Pointers relative to the schema of the option, thus a client can tell a default that comes from a defaultText from a default that 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 is sensitive, 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.md describes how api serves this schema. This specification describes only what core produces.
  • 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 partial setting is given to the operator in full, not only the part that is for the operator (#2132).