profiles: key shape check off flakeExposed, validate at consumed surface #4

Merged
vnprc merged 1 commit from agent/profile-shape-flakeexposed into master 2026-07-20 17:13:28 +01:00
Member

Closes allod/profiles#3

Removes the hardcoded architecture list from the profile shape check and moves the shape contract onto the surface the framework actually consumes, without adding an inventory or secrets input. The check body never read the architecture, so the list was scaffolding to satisfy the system-keyed checks schema, not machine policy; and because nothing downstream consumes profiles.checks, the old standalone-only check gave no protection to the scenario the repo exists to serve — an operator redirecting the framework's profiles input at their own definitions repo.

Changes

  • De-literalize the check keying: replace checkSystems = [ "x86_64-linux" "aarch64-linux" ] with nixpkgs.lib.systems.flakeExposed, so no literal here reads as a machine-platform-policy list.
  • Validate at the consumed surface: bind the shape asserts once into a single guarded checkedProfileDefinitions and expose it as lib.profileDefinitions, so any consumer (the archetypes framework at composition time) trips the contract on read — not only a standalone nix flake check in this repo.
  • Preserve the standalone signal: the retained profile-contract-shape check forces the shared binding via builtins.seq, so nix flake check here still exercises it.
  • Keep the charter: only nixpkgs, no inventory/secrets input; no change to composition behavior for valid definitions.

The three footguns called out in the issue are encoded as code comments: builtins.seq forces only weak-head-normal-form (upgrade to builtins.deepSeq if deeper validation is added later); the flakeExposed breadth is deliberate and non-policy; and lib.profileDefinitions must stay a single checked binding or the consumption-time guarantee erodes.

Risk

Low (R1). flake.nix-only in a single data-definitions repo, with no composition-behavior change: lib.profileDefinitions returns the same attrset value for valid input (the guard is transparent), and profileData / homeModules.preferences are untouched. The behavioral delta is strictly earlier, louder failure — a malformed definition now fails at the consumed surface instead of surfacing later as a framework-internal composition error. nix flake check now evaluates checks across all flakeExposed systems, so nix flake show / --all-systems are slower and the check nominally exists on darwin/riscv; it still builds only the host check. Fully recoverable by revert.

Validation

  • nix flake check passes: builds checks.x86_64-linux.profile-contract-shape and warns it omitted the incompatible foreign systems (expected).
  • Positive: nix eval .#lib.profileDefinitions --apply builtins.isAttrs -> true; forcing the consumed surface passes the asserts.
  • Negative: injecting nixosModules = "notalist" into a definition makes nix eval .#lib.profileDefinitions throw profile contract shape errors: profileDefinitions.privacy.privacy-1.nixosModules is not a list, confirming the contract now fires at the surface the framework reads, not only in a standalone check.
  • .#checks is keyed by flakeExposed (10 systems this nixpkgs release, including darwin / riscv / freebsd).
Closes allod/profiles#3 Removes the hardcoded architecture list from the profile shape check and moves the shape contract onto the surface the framework actually consumes, without adding an `inventory` or `secrets` input. The check body never read the architecture, so the list was scaffolding to satisfy the system-keyed `checks` schema, not machine policy; and because nothing downstream consumes `profiles.checks`, the old standalone-only check gave no protection to the scenario the repo exists to serve — an operator redirecting the framework's `profiles` input at their own definitions repo. ## Changes - De-literalize the check keying: replace `checkSystems = [ "x86_64-linux" "aarch64-linux" ]` with `nixpkgs.lib.systems.flakeExposed`, so no literal here reads as a machine-platform-policy list. - Validate at the consumed surface: bind the shape asserts once into a single guarded `checkedProfileDefinitions` and expose it as `lib.profileDefinitions`, so any consumer (the archetypes framework at composition time) trips the contract on read — not only a standalone `nix flake check` in this repo. - Preserve the standalone signal: the retained `profile-contract-shape` check forces the shared binding via `builtins.seq`, so `nix flake check` here still exercises it. - Keep the charter: only `nixpkgs`, no `inventory`/`secrets` input; no change to composition behavior for valid definitions. The three footguns called out in the issue are encoded as code comments: `builtins.seq` forces only weak-head-normal-form (upgrade to `builtins.deepSeq` if deeper validation is added later); the `flakeExposed` breadth is deliberate and non-policy; and `lib.profileDefinitions` must stay a single checked binding or the consumption-time guarantee erodes. ## Risk Low (R1). `flake.nix`-only in a single data-definitions repo, with no composition-behavior change: `lib.profileDefinitions` returns the same attrset value for valid input (the guard is transparent), and `profileData` / `homeModules.preferences` are untouched. The behavioral delta is strictly earlier, louder failure — a malformed definition now fails at the consumed surface instead of surfacing later as a framework-internal composition error. `nix flake check` now evaluates checks across all `flakeExposed` systems, so `nix flake show` / `--all-systems` are slower and the check nominally exists on darwin/riscv; it still builds only the host check. Fully recoverable by revert. ## Validation - `nix flake check` passes: builds `checks.x86_64-linux.profile-contract-shape` and warns it omitted the incompatible foreign systems (expected). - Positive: `nix eval .#lib.profileDefinitions --apply builtins.isAttrs` -> `true`; forcing the consumed surface passes the asserts. - Negative: injecting `nixosModules = "notalist"` into a definition makes `nix eval .#lib.profileDefinitions` throw `profile contract shape errors: profileDefinitions.privacy.privacy-1.nixosModules is not a list`, confirming the contract now fires at the surface the framework reads, not only in a standalone check. - `.#checks` is keyed by `flakeExposed` (10 systems this nixpkgs release, including darwin / riscv / freebsd).
Replace the literal checkSystems arch list with
nixpkgs.lib.systems.flakeExposed so no literal here reads as
machine-platform policy, and hoist the shape asserts onto a single
guarded lib.profileDefinitions binding so any consumer (the archetypes
framework at composition time) trips the contract, not only a standalone
nix flake check in this repo. The retained profile-contract-shape check
forces that binding via builtins.seq to preserve the standalone signal.
No inventory or secrets input; no change to composition behavior for
valid definitions.
Author
Member

End-to-end composition check via the deploy flake (--override-input profiles path: at this PR branch), confirming the consumed-surface guarantee through the real archetypes framework rather than only this repo's standalone check:

  • Positive — nix eval <deploy>#nixosConfigurations.allod-dev.config.system.build.toplevel.drvPath --override-input profiles path:<profiles> composes cleanly to nixos-system-allod-dev-…drv (exit 0). Valid definitions are unaffected; allod-dev builds identically.
  • Negative — injecting nixosModules = "notalist" into a definition makes the framework's own composition throw during nixosConfigurations evaluation (archetypes flake.nix:366, // machineConfigurations): error: profile contract shape errors: profileDefinitions.privacy.privacy-1.nixosModules is not a list (exit 1).

So a malformed definition is now rejected at the framework composition surface a consumer actually evaluates, which is the behavior this issue asks for.

End-to-end composition check via the deploy flake (`--override-input profiles path:` at this PR branch), confirming the consumed-surface guarantee through the real archetypes framework rather than only this repo's standalone check: - Positive — `nix eval <deploy>#nixosConfigurations.allod-dev.config.system.build.toplevel.drvPath --override-input profiles path:<profiles>` composes cleanly to `nixos-system-allod-dev-…drv` (exit 0). Valid definitions are unaffected; allod-dev builds identically. - Negative — injecting `nixosModules = "notalist"` into a definition makes the framework's own composition throw during `nixosConfigurations` evaluation (`archetypes flake.nix:366`, `// machineConfigurations`): `error: profile contract shape errors: profileDefinitions.privacy.privacy-1.nixosModules is not a list` (exit 1). So a malformed definition is now rejected at the framework composition surface a consumer actually evaluates, which is the behavior this issue asks for.
vnprc approved these changes 2026-07-20 17:13:22 +01:00
vnprc merged commit e42709b794 into master 2026-07-20 17:13:28 +01:00
vnprc deleted branch agent/profile-shape-flakeexposed 2026-07-20 17:13:28 +01:00
Sign in to join this conversation.
No description provided.