Feature Specification: Resource family
Created: 2026-10-07
Status: Implemented
Input: Reconstructed from the code of core/resources/, core/lib/ and core/setups/ at 093e68b6f, pull requests #1184, #1234, #1235 and #1241.
User Scenarios & Testing (mandatory)
This spec covers what all resources share. The general model of a resource is in 001 Data Model, and the wiring that calls the policies is in 005 Setups and wiring. Each resource has its own spec, listed in the Index.
User Story 1 - Add a resource to the registry (Priority: P1)
Components reach every external need through a resource. A developer adds a resource with a description, a request type and a policy type. A component requests it under an instance key, and an environment holds a policy for it under a label.
Independent Test: each resource has its own unit suite, and a unit test
of the data model covers the input types of apply and process.
Acceptance Scenarios:
- Given a policy with the default
process, When a setup calls it, Then the policy receives one list of the matching requests. - Given an argument of the wrong outer shape to
applyorprocess, When it evaluates, Then the evaluation fails and names the option and the type.
User Story 2 - Select a provider per deployment (Priority: P1)
A wrong provider selection gives a node a provider that does not exist. A
contract-backed policy carries a routing module, which the setup fills as
a default from one routing table, one row per resource. A test can override
the row.
Independent Test: a unit test of the routing table.
Acceptance Scenarios:
- Given a node that consumes a contract resource, When its deployment does not provide it, Then the evaluation fails and names the node and the resource.
- Given a resource that no node consumes, When the deployment does not provide it, Then the evaluation does not fail.
User Story 3 - Collect requests that no single component owns (Priority: P2)
Shared provider nodes of the hosting provider (garage, postgresql,
openbao, authelia) serve other groups and must know what they ask for.
The setup evaluates a requesting group once and collects the processed
requests of all its nodes per resource as supportingRequests.
Independent Test: a unit test of the collection.
Acceptance Scenarios:
- Given an operator group that requests a resource, When the provider node evaluates, Then the collection holds the request, and only that group is evaluated.
Edge Cases
- A resource that wraps no contract (
firewall,network,nixos-module,otel,pod-roles) only adds NixOS configuration to the consumer node, because there is no provider to select. garageroutes outside the routing table.- A contract resource can count as provided with no provider node in the group:
garageandsslalways,oidc,postgresqlandredisthrough another group,smtpthrough a third-party relay,netboxanddnsthrough an external service, andspiffethrough the hosting provider. - The missing-provider check does not cover a resource without a consumer side (
firewall,generateFiles,network,nixos-module,otel,pod-roles,secrets). - A policy that needs the component and instance keys overrides
processand declares the shape thatapplytakes:generateFilesby instance,secretsby app and secret,smtpby app.postgresqloverridesprocessbut givesapplya list. - A policy does not combine the requests of different components (FR-005).
supportingRequestscollects across components and groups, and the contract provider combines them.
Requirements (mandatory)
Functional Requirements
- FR-001: The registry MUST hold every resource:
oidc,dns,firewall,garage,generateFiles,ldap,netbox,nixos-module,network,pod-roles,otel,postgresql,redis,secrets,smtp,spiffe,ssl. - FR-002: Each resource MUST have a description, a request type and a policy type.
- FR-003: Each policy MUST declare its resource type and an
apply. The setup MUST reach a policy only throughprocess. - FR-004: The default
processMUST select the requests for its resource, flatten them over components and instance keys, and give the list toapply. - FR-004a:
processMUST take the requests by component, then by instance key.applyMUST take the input type that the policy declares, a list by default. A call of either MUST check the outer shape of its argument layer by layer, without a merge through the type, so the requests are not evaluated again. - FR-005: A setup MUST give
processonly the requests of the component on its node. - FR-006: A contract-backed policy MUST take its provider selection from a
routingmodule that the setup sets as a default. - FR-007: A coordinate resource MUST resolve its contract in the evaluation of the consumer: the consumer imports the contract, the provider modules and
routing, and states its want. - FR-008: A coordinate provider MUST declare its settings per flavor and register its provider module, which stays inert until a
routingrow selects it. - FR-009: A node that consumes a contract resource without a provider in its deployment MUST fail evaluation, with one error per resource. A contract resource is one whose policy declares a consumer side, and the set MUST come from the policy declarations. A contract resource for which the routing cannot tell whether it is provided MUST fail evaluation.
- FR-010:
supportingRequestsMUST be a deferred module of its own class that every environment can read. - FR-011: A collection of supporting requests MUST evaluate only the group it reads.
- FR-012: Each pure decision of a resource MUST have a described unit suite.
Key Entities
- Request: the value a component sets for a resource under an instance key.
- Policy: the value an environment sets for a resource under a label, such as
"database"forpostgresql. It holds the resource type, the input type ofapply,applyandprocess. routing: a deferred NixOS module in a contract-backed policy that selects and configures a provider.- Consumer side: the NixOS modules that a contract-backed
applyreturns for the consumer node.
Success Criteria (mandatory)
Measurable Outcomes
- SC-001: The unit suites of all resources, the routing table and the supporting-request collection pass.
- SC-002: Every resource in FR-001 has an item spec in the Index.
Assumptions
- The setup gives each node the policies of one environment.
- A component that needs a resource sets a request. It does not read the configuration of another node.
- The contract types come from the vendored contracts library.
Known Deviations
None.