nix-tf-schema Constitution
Core Principles
I. Consumer-Agnostic Library
nix-tf-schema translates OpenTofu/Terraform provider schemas into Nix module
types and Terraform JSON. It MUST NOT know the domain of any project that
consumes it. Names, defaults, generated output, comments, examples and test
data MUST NOT refer to a consumer's business concepts (for example fediversity
deployments, applications, hosting providers or operators), nor to a
consumer's directory layout, file names or internal workflows. A public
provider (for example hashicorp/external) MAY appear in an example or a
test, because it illustrates the Terraform side and not a consumer.
A fact that only one consumer needs (for example which state backend to use) MUST come in as a parameter or stay in that consumer.
Rationale: the library is separate from the fediversity monorepo so that other Nix + Terraform projects can use it. The dependency goes one way only: a consumer knows the library, the library does not know a consumer. Knowledge of one consumer's internals makes the library break or behave strangely when that consumer changes, and makes it less useful to the others.
II. Translation Only
The library is a translation layer plus a small set of authoring primitives
(references, resource meta-arguments). It MUST NOT orchestrate deployments,
keep Terraform state, handle secrets, or deliver values to Terraform. It MUST
NOT import code from a consumer; a helper that it needs from a consumer is
vendored (as lib.nix does for cast and evalOption).
Rationale: a narrow scope keeps the library small, testable with nix-unit,
and usable by consumers with different run-time designs.
III. Types at Evaluation, Values at Run Time
Schema-derived options describe values that reach Terraform at run time as
variables (for example through TF_VAR_*). They MUST NOT accept Terraform
reference expressions, because Terraform reads a variable value literally.
References are for the hand-written parts of a module only. Meta-arguments
(count, for_each, depends_on, provider, lifecycle) MUST render into
the resource body and MUST NOT become variables. Generated variable and
resource JSON MUST be such that an unset value behaves as an omitted argument.
Rationale: this is where the library differs from tools that bake literal
values into the module JSON (for example tofunix). A ${...} in a variable
value lands in the plan unchanged, and Terraform resolves meta-arguments
before it knows variables.
IV. Both Schema Dialects in One Walk
Every schema walk MUST support both provider schema dialects
(terraform-plugin-sdk/v2 blocks and terraform-plugin-framework nested
attributes), also in one resource. The walk MUST produce one node shape for
both, so that a consumer does not branch on the dialect. A translation that
loses information (for example int64 as a 32-bit integer, set as a list)
MUST be documented where it happens and in the README.
Rationale: one resource can mix the two dialects, so two parallel readers would disagree at the seams.
V. Traceable Failures
A schema walk MUST tag each recursion step with an error context that names
the resource, attribute or block, prefixed with [nix-tf-schema]. A new walk
MUST do the same.
Rationale: an evaluation failure deep in a large provider schema is otherwise
a bare attribute 'x' missing that does not say where it comes from.
VI. One Entrypoint on the Consumer's nixpkgs
default.nix is the single source of truth for all outputs. It is
callPackage-shaped, so a consumer passes its own pkgs and lib and
evaluation uses the consumer's nixpkgs. The helpers are under lib, so they
do not clash with the flake-schema outputs. flake.nix is a thin wrapper that
adds the ${system} layer, instantiates the entrypoint once per system, and
exposes lib without a system key. It MUST NOT add, remove or change outputs.
VII. Sandboxed and Pinned
A helper that runs a provider binary (schema extraction) MUST run in the Nix build sandbox without network access. All inputs (nixpkgs, treefmt-nix, nix-docs-lib, npins) are pinned with npins. Consumers pin nix-tf-schema with npins as well, so a change here reaches a consumer only when that consumer updates its pin.
Constraints
- The public surface is the attribute set under
lib(the conversion helpers) of thedefault.nixentrypoint and of the flake. A change to the name, arguments or output of a helper in that surface is a breaking change for consumers and MUST be named as such in the pull request. The file paths in the repository (for exampleconversion.nix) are not part of the public surface: a consumer that imports a file directly must change to the entrypoint. - Every public helper has a doc-comment that the documentation site renders. Cross-references between doc-comments MUST use the anchors that the site generates.
- The
nix-unitsuite inconversion-test.nixcovers every public helper. It runs outside the Nix build sandbox, because the schema extraction test needs import-from-derivation.
Development Workflow
- Changes go through pull requests on git.fediversity.eu. The
nix-unit,treefmtanddocsCI jobs MUST pass. - A pull request that changes behavior updates the matching specification under
specs/in the same pull request. - Documentation (README,
md/, doc-comments) describes the current state of the code. - Prose in comments, commit messages and pull requests uses ASD-STE100, US English and ASCII punctuation, with backticks around code.
- A pull request that adds a helper for one consumer MUST show why the helper is general. If it is not general, it stays in that consumer.
Governance
This constitution has priority over other practices in this repository. An amendment is a pull request that changes this file, updates the version and the Sync Impact Report, and updates any specification that the amendment affects. Versions follow semantic versioning: MAJOR for a removed or redefined principle, MINOR for a new principle or section, PATCH for clarifications.
Version: 1.0.0 | Ratified: 2026-09-22 | Last Amended: 2026-09-22