Day 56 — Stage V gate
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.
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.descriptionMissing 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 .#cooltoolStep 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/failStep 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 existsStep 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.mdcomplete
- 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).