Feature Specification: Data Model and Extension Seam
Created: 2026-09-22
Status: Implemented
Input: Reconstructed from the code at 0bf4ecd36 (core/lib/data-model.nix, core/compose.nix, core/resources/, core/components/, core/lib/env-adapter.nix), pull requests #1184, #1202, #1077, #1822, #1828 and #1831, issues #527 and #1189, and the design notes of the maintainers.
User Scenarios & Testing (mandatory)
User Story 1 - Describe a deployable service as a component (Priority: P1)
A developer adds a service. The developer declares it as a component: a
description, a NixOS module, the package, the file of its artwork (artwork, which gives an icon with an optional variant
for dark backgrounds), whether an operator can turn it on (optional), whether the operator form shows it (configurable, which defaults to optional), and an optional UI subdomain. The component
states the resources it needs (for example PostgreSQL, S3, OIDC, SMTP, DNS,
secrets). The data model then offers it in the configuration, and the
setups can deploy it.
Why this priority: all other capabilities of core (catalog, schema,
deployment programs) read the components. Without them, nothing deploys.
Independent Test: add a component with optional = true in a test
module. The evaluated model lists it under applications, and the
configuration type has an option for it.
Acceptance Scenarios:
- Given a component with
configurable = true, When the model is evaluated, Thenapplicationscontains it andfrontendConfigurationhas an option for it. - Given a component with
configurable = false, When the model is evaluated, Thenconfigurationhas an option for it andfrontendConfigurationdoes not. - Given a component without a
package, When it does not state adescription, Then the evaluation fails with an error that names the component.
User Story 2 - Satisfy a resource request with a provider policy (Priority: P1)
A component requests a resource (for example a PostgreSQL database). An
environment has a policy for that resource type. The policy checks the request
and selects the provider that satisfies it (process, apply, routing). The
resource wraps a contract, so the provider is replaceable. The provider, not
the policy, combines the requests of different components.
Why this priority: resources are the bridge between what an application needs and what a hosting environment has.
Independent Test: evaluate a deployment with one component that wants a PostgreSQL database and an environment with the PostgreSQL policy. The provider gets the request and the component gets the connection data.
Acceptance Scenarios:
- Given a request for a resource and a provider for it in the deployment, When the deployment is evaluated, Then the provider receives the request and the consumer gets the result.
- Given a request for a contract that no provider in the deployment satisfies, When the deployment is evaluated, Then the evaluation fails and names the missing provider.
- Given a provider that no component selects, When the deployment is evaluated, Then the provider stays inactive.
User Story 3 - Extend the model from a front-end or a provider (Priority: P2)
A front-end has its own components (for example its own web service). A
hosting provider has its own values for the environments. Both extend the
model without a change to core: the front-end folds its components,
modules and machines into the groups with lib.foldGroupComponents,
lib.foldGroupModules or lib.foldGroupExtensions, and renders the result. The provider gives a hosting
configuration file (.nix, .toml or .json) with environments and
effect-defaults.
Why this priority: it is the one seam that keeps core free of
front-end knowledge (constitution principles I and IV).
Independent Test: fold a test component into lib.baseGroups and call
lib.render. The rendered outputs contain deployment programs for it.
Acceptance Scenarios:
- Given extra modules passed to
render, When the outputs are evaluated, Then the model contains their components and policies. - Given a hosting configuration that sets a policy value, When it is merged, Then the value of the provider wins over the default.
- Given a TOML hosting configuration with a value of the wrong type, When it is merged, Then the evaluation fails with a type error of that option.
Edge Cases
- TOML cannot express
nullor functions. A provider that needs such values uses the.nixform. - Two modules declare different metadata for the same environment variable. The evaluation fails.
- A component name is not the name of a node. The same component can run on another node, so wiring does not read node names (constitution principle V).
Requirements (mandatory)
Functional Requirements
- FR-001: The model MUST declare
components,resources,environments,configuration,frontendConfigurationandsupportingRequestsas options of the module system. - FR-002:
applicationsMUST be derived, read-only, from the components that areconfigurable. - FR-003:
frontendConfigurationMUST contain only theconfigurablecomponents.configurationMUST contain all components. - FR-004: A resource MUST have a request type and a policy. A policy MUST say how a request is applied and processed.
- FR-005: The evaluation MUST fail when a component wants a contract that no provider in the deployment satisfies.
- FR-006: A provider MUST stay inactive when no request selects it.
- FR-007: The model MUST accept extra modules through one seam (
render's extra modules and the fold helpers), and MUST NOT offer other override parameters. - FR-008: The hosting configuration MUST merge over the defaults, and its values MUST have the types of the options they set.
- FR-009: Each module that reads an environment variable MUST declare it with a description and a default. The model MUST fail on conflicting declarations.
- FR-010: Wiring decisions MUST read what a component provides or needs, not its node name.
Key Entities
- Component: a deployable service with a description, module, package, artwork file (an icon and an optional dark icon),
optionalandconfigurableflags, optional UI subdomain, and the capabilities it provides. - Application: a component that an operator can turn on. The wire name
applicationsstays for compatibility with stored configurations. - Resource: a need of a component (DNS, firewall, S3, generated files, LDAP, NetBox, network, OpenTelemetry, PostgreSQL, Redis, secrets, SMTP, SSL, OIDC), with a request type and a policy. It wraps a contract.
- Environment: the policies, implementation and mappings of one hosting target.
- Hosting configuration: the file of a provider that sets environment values and effect defaults.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001:
apiandpaneladd their own components tocorethrough the fold helpers only. - SC-002: The
nix-unit-lib-*,nix-unit-env-adapter,nix-unit-resources-*andcontracts-*checks pass. - SC-003: A deployment with a missing contract provider fails at evaluation, not at run time.
Assumptions
-
The contracts library under the repository root
nix/contractsis shared infrastructure, not a sub-project. -
Operator authentication (the single sign-on of the deployed applications) is part of the deployed product.
-
Cross-node contracts are part of the model.
-
To group resources by type is a possible goal. A flake gives its outputs as a flat attribute set, so a model with such groups needs a way to make them flat.
-
To make a deployment a resource of an environment is not a goal, because no use for it is known.
Known Deviations
None.