Builders should not close over the data inputs #92

Open
opened 2026-09-21 22:59:14 +01:00 by allod-agent · 0 comments
Member

Make the three archetype builders pure functions of their arguments, so a builder is structurally incapable of reading the secrets, profiles or inventory inputs rather than merely pinned against doing harm with them.

Primary goals:

  • No closure over a data input — mkDevVm, mkPrivacyVm and mkHypervisor take every data-derived value as a required argument; machines, profiles, secretsInput, piProviderCatalog and piProviderCredentials are not in scope inside them.
  • The boundary readable from the signature — what a builder consumes is its argument list, not a mix of arguments and defaults reaching into three repositories.
  • A capability removed, not a rule added — nothing new guards anything; the composition becomes the only place in the flake that reads a machine fact.

Current state and specifics

allod/archetypes#88 widened the builder seam so a check could construct a fixture machine without borrowing a real machine's name, and a review of that work found the cost: machineConfigurations (flake.nix:735) merges profileData and inventory's profile settings into builder arguments, so every parameter is also a key the profiles layer can set. productionFactContract closes that by applying the canonical values after the merge.

The guard is correct and is not what this issue changes. What it leaves untouched is that the builders still reach into the data inputs for their defaults:

Default Line Reaches
secrets ? secretsInput flake.nix:321, :525, :561 the secrets flake input
preferencesModule ? profiles.homeModules.preferences flake.nix:324, :575 the profiles input
piCatalog ? piProviderCatalog flake.nix:340 the profiles input
piCredentialMap ? piProviderCredentials flake.nix:341 the secrets input
runtime ? machines.${name}.runtime flake.nix:348, :526 the inventory input
platform ? machines.${name}.platform flake.nix:356, :527, :562 the inventory input
hardware ? machines.${name}.hardware flake.nix:563 the inventory input

Once secrets is an argument, the defaults derived from it — runtimeIdentity, gitPolicySource, githubCredentialTargets — follow the argument and are already clean; the same holds for everything derived from identity. So the change is the seven rows above becoming required, machineConfigurations supplying runtime and preferencesModule alongside the values it already pins, and the fixture call sites in the checks passing what they already mostly pass.

Two things a future reader should not have to rediscover. First, this does not retire productionFactContract: what protects a canonical fact from profileData is where the value is applied, after the merge, not whether the parameter carries a default. Second, the builders are internal — lib exports composeProfileDefinitions, composedLayerCheck and validateComposedLayerSources, not the builders — so no consumer outside this repository is affected.

The witness is the one allod/archetypes#88 established and needs nothing new: every machine's derivation path must be unchanged, ./check.sh green, ./fork-safety.sh green.

Scope

In scope: the three builder signatures, the argument set machineConfigurations passes, and the fixture call sites in the checks that must now be explicit.

Tracked elsewhere: moving the inline checks out of flake.nix (allod/archetypes#93), and whether the fork witness should become a second composition (allod/archetypes#94).

Refs allod/archetypes#88

Make the three archetype builders pure functions of their arguments, so a builder is structurally incapable of reading the `secrets`, `profiles` or `inventory` inputs rather than merely pinned against doing harm with them. Primary goals: - **No closure over a data input** — `mkDevVm`, `mkPrivacyVm` and `mkHypervisor` take every data-derived value as a required argument; `machines`, `profiles`, `secretsInput`, `piProviderCatalog` and `piProviderCredentials` are not in scope inside them. - **The boundary readable from the signature** — what a builder consumes is its argument list, not a mix of arguments and defaults reaching into three repositories. - **A capability removed, not a rule added** — nothing new guards anything; the composition becomes the only place in the flake that reads a machine fact. ### Current state and specifics allod/archetypes#88 widened the builder seam so a check could construct a fixture machine without borrowing a real machine's name, and a review of that work found the cost: `machineConfigurations` (`flake.nix:735`) merges `profileData` and inventory's `profile` settings into builder arguments, so every parameter is also a key the profiles layer can set. `productionFactContract` closes that by applying the canonical values after the merge. The guard is correct and is not what this issue changes. What it leaves untouched is that the builders still reach into the data inputs for their defaults: | Default | Line | Reaches | | --- | --- | --- | | `secrets ? secretsInput` | `flake.nix:321`, `:525`, `:561` | the secrets flake input | | `preferencesModule ? profiles.homeModules.preferences` | `flake.nix:324`, `:575` | the profiles input | | `piCatalog ? piProviderCatalog` | `flake.nix:340` | the profiles input | | `piCredentialMap ? piProviderCredentials` | `flake.nix:341` | the secrets input | | `runtime ? machines.${name}.runtime` | `flake.nix:348`, `:526` | the inventory input | | `platform ? machines.${name}.platform` | `flake.nix:356`, `:527`, `:562` | the inventory input | | `hardware ? machines.${name}.hardware` | `flake.nix:563` | the inventory input | Once `secrets` is an argument, the defaults derived from it — `runtimeIdentity`, `gitPolicySource`, `githubCredentialTargets` — follow the argument and are already clean; the same holds for everything derived from `identity`. So the change is the seven rows above becoming required, `machineConfigurations` supplying `runtime` and `preferencesModule` alongside the values it already pins, and the fixture call sites in the checks passing what they already mostly pass. Two things a future reader should not have to rediscover. First, this does not retire `productionFactContract`: what protects a canonical fact from `profileData` is *where* the value is applied, after the merge, not whether the parameter carries a default. Second, the builders are internal — `lib` exports `composeProfileDefinitions`, `composedLayerCheck` and `validateComposedLayerSources`, not the builders — so no consumer outside this repository is affected. The witness is the one allod/archetypes#88 established and needs nothing new: every machine's derivation path must be unchanged, `./check.sh` green, `./fork-safety.sh` green. ### Scope In scope: the three builder signatures, the argument set `machineConfigurations` passes, and the fixture call sites in the checks that must now be explicit. Tracked elsewhere: moving the inline checks out of `flake.nix` (allod/archetypes#93), and whether the fork witness should become a second composition (allod/archetypes#94). Refs allod/archetypes#88
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/archetypes#92
No description provided.