Feature Specification: Configuration Model and JSON Schema

Created: 2026-09-22

Status: Implemented

Input: Reconstructed from the code at 0bf4ecd36 (core/applications-type.nix, core/lib/schema-for-source.nix, core/lib/json-schema.nix, core/lib/renames-for-source.nix, core/lib/option-title.nix, core/lib/option-selection.nix, core/components/operator/*/default.nix, core/components/operator/operator-facing-options.csv, core/effects/common/submitted-config.nix, core/default-configuration.json, core/render/outputs.nix), pull requests #1219, #1289, #1839 and #1843, commit c58cd36b7, and api/specification.md.

User Scenarios & Testing (mandatory)

User Story 1 - Get the schema of the operator configuration (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 need to know the names of the applications or their options.

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 frontend-configuration-schema package. The output 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 (provider-configuration-schema) is built, Then the fixed field is not in the schema.
  3. 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.
  4. 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.
  5. Given the provider schema, When provider-application-schemas is built, Then it has one schema for each application, and each schema validates a value for its application without the full schema.

User Story 2 - Read the configuration type without the setups (Priority: P1)

A front-end validates a submitted configuration with the same type that the deployment uses. It evaluates lib.applications-type, which is built from the components alone, and does not evaluate the setups.

Independent Test: compare the hash of applications-type with the hash of frontendConfiguration of each deployment method. They are equal.

Acceptance Scenarios:

  1. Given the components, When applications-type is evaluated, Then it is equal to the frontendConfiguration type 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 (configuration-renames) of from and to paths, so the front-end can migrate the stored values.

Independent Test: rename an option with mkRenamedOptionModule in a test module. The rename table has the entry, and the schema does not show the old name.


User Story 4 - Validate a value against a schema that an application publishes (Priority: P3)

An application (PeerTube, Mastodon and Pixelfed) publishes a JSON Schema for its own settings. The component of that application narrows the 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: validate 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 * (for example DEEPL_*), When the selection is resolved, Then it selects each setting whose name starts with the part before the *.
  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 carry x-env (Pixelfed), When the schema is keyed by environment variable, Then keys that read the same variable become one property, and keys with types that do not agree fail.

Edge Cases

  • A schema of another dialect, a non-local $ref, an anchor $ref, or a $ref with siblings: the validator fails with a named error.
  • pattern is ECMA-262, and the match uses POSIX extended regular expressions. The validator first restates in POSIX ERE the ECMA-262 escapes that POSIX ERE spells differently (\d, \s, \w and their negations, \n, \r, \t, an escaped punctuation character) and a lazy quantifier. A construct that has no POSIX ERE equivalent, such as a lookahead or \b, makes the evaluation fail with a named error. Lengths count bytes, and format is not asserted. The test suite records each case of the official suite that fails for these reasons.
  • Without --impure, provider-configuration-schema cannot read HOSTING_CONFIG and PIN_EFFECT, and gives the schema without the prune and the collapse.
  • A submission names only some applications. The named applications replace the baked entries, with enable as an explicit boolean. The other applications keep the baked entries.

Requirements (mandatory)

Functional Requirements

  • FR-001: core MUST publish the JSON Schema (draft 2020-12) of the operator configuration, generated from the module options by module-schema.
  • FR-002: The provider schema MUST NOT show fields that the hosting configuration fixes, and MUST NOT show renamed (alias) options.
  • FR-003: The schema MUST have a root default: the configuration a new deployment starts from, with each option at the default that its module declares.
  • FR-004: lib.applications-type MUST be built from the components alone and MUST be equal to the frontendConfiguration 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 do not have a 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. core/components/operator/operator-facing-options.csv 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 (lib.schemaForSource.applicationSchemas, output provider-application-schemas), so a consumer does not walk the schema itself.

Key Entities

  • Operator configuration: the domain, the contact email and the applications set of one deployment.
  • Schema: the JSON Schema of the operator configuration, with a root default.
  • Operator options audit: operator-facing-options.csv, one row for each option or setting of an application, with its audience, whether it is only partly for the operator, whether it is a secret, and notes.
  • Rename table: a list of { from, to } option paths.
  • Default configuration: core/default-configuration.json, 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 nix-unit-lib-json-schema, nix-unit-lib-json-schema-suite, nix-unit-lib-renames-for-source, nix-unit-lib-option-title, nix-unit-lib-option-selection, nix-unit-effects-submitted-config and nix-unit-setups-operator-config checks pass.
  • SC-003: The validator passes all cases of the official JSON Schema suite, except the cases that the suite test records.

Assumptions

  • api/specification.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). core/default-configuration.json, the seed of the setups and the tests, turns off every application.

Known Deviations

  • A secret that the operator brings (a row with sensitive set to yes, for example DEEPL_API_KEY) is a plain form field (#2133). Its value goes into the Nix store and into the stored configuration of the deployment.
  • A setting with partial set to yes in operator-facing-options.csv is given to the operator in full, not only the part that is for the operator (#2132).