Day 56 — Stage V gate

Updated

July 30, 2026

Day 56 — Stage V gate

Stage V · ~4h (integration)
Goal: Prove packaging craft end-to-end: a flake-exported package you use, built from lock alone, with a cache/substitute story and notes another engineer could follow—on a nixos-26.05 pin.

Important

Gates are not ceremonies. If G1–G8 fail, stay in Stage V. Tourism into Disko while your package only builds on a dirty laptop store is how fleets inherit shame.

Why this day exists

Stages without gates rot into tutorial tourism. Exit Stage V only when someone else (or future you on a clean VM) can realize your package without tribal knowledge. Stage VI multiplies packaging mistakes across hosts and images.


Gate bar (explicit)

# Requirement Evidence
G1 Package expression is yours (or substantially maintained by you) Path under your flake
G2 Language helper or clean stdenv—not a mystery binary copy Source + builder
G3 FODs pinned (src + vendor/cargo/npm as needed) Hashes in git
G4 nix build .#… works from clean checkout + lock Transcript / CI
G5 Consumed by host or CI or documented nix run entry Config snippet
G6 Cache story: push, file-store, or public cache hit path Day 53 artifact
G7 Debug notes + REPRO honesty linked Days 51 & 55
G8 Meta: license, mainProgram, description nix eval …meta

Optional stretch: overlay integration on lab NixOS; multi-output library; nix flake check includes a run test.


Theory 1 — Integration layout

flake.nix
  packages.<system>.cooltool
  checks.<system>.*
  overlays.default
  apps.<system>.default
  nixosConfigurations.lab → environment.systemPackages = [ cooltool ]
  # or just apps/run for non-NixOS
# modules/cooltool-host.nix
{ pkgs, ... }:
{
  environment.systemPackages = [ pkgs.cooltool ];
}
# in flake, when composing host pkgs:
pkgs = import nixpkgs {
  inherit system;
  overlays = [ self.overlays.default ];
};

One pkgs instance rule

Overlays that exist only on packages.* but not on nixosConfigurations produce “works in nix run / fails on host” ghosts. Wire one overlay path for all consumers.


Theory 2 — What “yours” means (G1 honesty)

Acceptable Reject
Fork of a nixpkgs expr you maintain Unmodified pkgs.hello
Thin wrapper around upstream you understand Copy-paste you cannot explain
buildGoModule / buildRustPackage you authored Prebuilt blob with no source story
Vendored FODs you can re-hash Network fetch without hash

If the package is trivial, make the process excellent: meta, checks, cache, docs.


Theory 3 — Clean-checkout proof

dirty laptop store  →  false confidence
clean VM + flake.lock only  →  gate truth
Proof quality Method
Best Second machine / fresh VM
Good CI from clean runner
Weak Same machine after git clean only
Fail “It builds here” with untracked files

Worked example — gate flake skeleton

{
  description = "Stage V gate — cooltool";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";

  outputs = { self, nixpkgs }:
    let
      systems = [ "x86_64-linux" "aarch64-linux" ];
      forAllSystems = nixpkgs.lib.genAttrs systems;
      overlay = final: prev: {
        cooltool = final.callPackage ./pkgs/by-name/co/cooltool/package.nix { };
      };
    in {
      overlays.default = overlay;

      packages = forAllSystems (system:
        let pkgs = import nixpkgs { inherit system; overlays = [ overlay ]; };
        in {
          default = pkgs.cooltool;
          cooltool = pkgs.cooltool;
        });

      apps = forAllSystems (system: {
        default = {
          type = "app";
          program = "${self.packages.${system}.cooltool}/bin/cooltool";
        };
      });

      checks = forAllSystems (system:
        let pkgs = import nixpkgs { inherit system; overlays = [ overlay ]; };
        in {
          cooltool-runs = pkgs.runCommand "cooltool-runs" {
            nativeBuildInputs = [ pkgs.cooltool ];
          } ''
            cooltool --help >/dev/null || cooltool >/dev/null
            touch $out
          '';
        });

      # Optional host wiring if you maintain a lab configuration in-repo
      # nixosConfigurations.lab = …;
    };
}

Meta sanity

nix eval .#cooltool.meta.license.spdxId
nix eval .#cooltool.meta.mainProgram
nix eval .#cooltool.meta.description

Missing meta is a G8 fail even if the binary runs.


Lab — gate protocol (multi-step)

Suggested workspace: ~/lab/90daysofx/02-nixos/day56
(or promote your best day47–52 tree and freeze it here)

Step 1 — Select the candidate

One primary package. Delete half-finished side quests from the gate flake.

Step 2 — Clean tree check

git status
# commit or stash noise
nix flake metadata
nix flake check -L
nix build .#cooltool -L
nix run .#

Step 3 — Fresh environment proof

On a second user/VM:

git clone <your-repo> gate-verify && cd gate-verify
nix build .#cooltool -L
nix run .#

If second machine missing, use a disposable VM snapshot or CI. Prefer VM over heroic same-store tricks.

Step 4 — Cache path

Demonstrate one of:

  • Cachix/Attic push + pull on clean side
  • nix copy --to file://… + pull
  • Written exception if network policy blocks, with file-store proof
# example file-store
nix copy --to file:///tmp/gate-cache .#cooltool
# on clean side:
# nix copy --from file:///tmp/gate-cache .#cooltool

Step 5 — Host or CI consumption

Host: add package to environment.systemPackages via overlay; rebuild; which cooltool.
CI: minimal workflow file that builds the attr (full CI polish is Day 66—skeleton enough).

Step 6 — Gate dossier

Write GATE-V.md:

# Stage V gate dossier

## Package
- attr:
- source:
- builder:

## Build
- nix version:
- system:
- out path:
- nixpkgs pin (26.05 lock rev):

## Cache
- method:
- how to consume:

## Repro claim
- link to REPRO.md

## Debug
- link to runbook

## Consumer
- host snippet / CI snippet

## Exit criteria self-score
- G1–G8 table with pass/fail

Step 7 — Self-score ruthlessly

Any fail → fix same day or explicitly defer with reason (not silent). Deferred items become Stage VI risks—list them.

Step 8 — Tag

git tag -a stage-v-gate -m "Stage V packaging gate"
git push origin stage-v-gate  # if remote exists

Step 9 — Teach-back (optional but strong)

Explain FOD pins and overlay wiring out loud in under three minutes. If you cannot, the dossier is incomplete.

Step 10 — Consumer matrix

Consumer Wired? Command proof
packages.<sys>.cooltool nix build .#cooltool
apps / nix run nix run .#
Overlay → host which cooltool after rebuild
CI job URL or log path
checks nix flake check

At least two consumer rows must pass for a strong gate; G5 requires one, excellence requires more.


Red flags that fail the gate (automatic)

Red flag Why
Source path is /home/you/... without FOD Not portable
Binary copied from internet without hash Not a package, a ritual
Only builds with --impure Lock story broken
Overlay defined but never imported Host lies about availability
License unknown for redistributed code Meta/compliance hole

Common gotchas

Symptom / mistake What to do
Only works with dirty local store paths Use FODs; no /home/… src without copying into store
Gate package is pkgs.hello Reject—must be your maintenance surface
Cache not documented Fails G6
Checks not green Fix or remove flaky checks honestly
Overlay not connected to host pkgs Same pkgs instance everywhere
Secrets in expression Remove; rebuild trust
mainProgram wrong nix run breaks; fix meta
Multi-system attr missing Gate at least your lab system; note aarch64 stretch

Checkpoint

  • G1–G8 scored
  • Fresh build story exists
  • GATE-V.md complete
  • Tag or equivalent milestone marker
  • Ready for fleet habits without packaging shame

Commit

git add .
git commit -m "day56: stage V gate dossier and package freeze"

Write three personal gotchas before continuing.

Tomorrow

Day 57 — Disko for real: declarative disks for reinstall and cloud nodes (Stage VI begins).