SDLC & Minimal Policies#

A software development life-cycle and the smallest set of policies that make it real: for a solo practice whose production system is a family.

Read this as written

This is deliberately informal, yet formal enough. I write security and compliance frameworks for a living, and I know the difference between governance and governance-pretend. Everything below is a control I actually run, phrased at the altitude a one-person shop can sustain. If a line here ever stops matching what the repository does, the repository wins: fix the doc.


1. Purpose & Scope#

This document governs this flaketsunaminoai/nix-flake-final, the single Nix configuration that builds and operates every computer, server, and gadget in the household and at the family sites (Lafayette and Mom & Pop’s place in Bedford).

It is scoped to this repository because this is the one repo I maintain where a mistake doesn’t just cost me — it takes down the family’s Wi-Fi, DNS, documents, Home Assistant, and phones. That production obligation is what earns the extra formality.

In scope: changes to NixOS / nix-darwin / home-manager configuration, modules, packages, secrets, and the documentation in this repo, and their deployment to the live fleet.

Out of scope (governed only informally): my other repositories (see §2). They carry no family-availability obligation, so they run on the spirit of this SDLC without the ceremony.


2. The Repo Constellation#

This SDLC is written for this flake, but it’s informed by how I actually work and on the following:

Group Repositories Availability obligation
This homelab nix-flake-final High — the family depends on it daily
GRC / compliance work corp, praxis, PolicyPress (private, sc2in), security-center, internal-grc-system, ai-compliance, scf-api Professional — governed by their own controls
Zig experiments & forks atlas, clay-zig, tomlz, zig-time, zcloc, md4c, ggtag, haxo-rs, blackstripes, game-boy, tinyradio, lastchoice, and my build-system branches on DOOM, nmap, gnuradio, FluidX3D, CM1 None — pure R&D
Web / creative falseblue.com, craton.net, sc2.in, liturgy-latex, intaglio, ukiyoe, anikifm Low — mostly static on Cloudflare

Why only this one is ‘production’

Most of my repos are experiments, forks I’m porting to a Zig build, or content sites. This flake is the only one with live human dependents who are not me. Proportionality (below) is the whole point: match ceremony to blast radius, and this is the repo with the biggest blast radius at home.


3. Guiding Principles#

  1. Proportionality. Formality scales with blast radius. A change to my prompt theme is not a change to the household DNS resolver. Tier your changes (§7) and spend rigor where a failure hurts the family.
  2. Determinism & reproducibility. Everything is Nix. A host can be rebuilt from scratch and come back identical. This is the primary safety control — it makes every change reversible and every rollback boring.
  3. Everything-as-code, one source of truth. Configuration, secrets (sealed), documentation, compliance controls, and topology all live in this repo. If it isn’t in the flake, it isn’t real (and if it must live outside — cloudflared on voile, HAOS on the appliance — that exception is written down).
  4. Nothing lands unproven. No configuration reaches master without passing automated evaluation, and nothing family-critical reaches the fleet without building and, where applicable, passing the compliance VM test.
  5. Family-first operations. Deploys happen inside sane change windows, not during dinner, homework, or a movie. The people are the SLA.
  6. Honest self-governance. I am the developer, the reviewer, the approver, and the operator. I don’t pretend that’s segregation of duties — I name it as a risk and lean on automation as the compensating control (§5).

4. The Life Cycle#

The flow is intentionally small: branch → prove → merge → tag → converge → operate.

flowchart LR A[Plan / Issue] --> B[Branch] B --> C[Develop + local checks] C --> D[Push: fast eval gate] D --> E[PR to master: full build gate] E --> F[Self-review + merge] F --> G[release tag + branch advance] G --> H[Fleet converges / deploy-rs] H --> I[Operate / Monitor] I -->|rollback if needed| H I --> A

4.1 Plan / Intake#

Work starts as a GitHub issue (or a note in project memory for smaller things). Classify the change’s tier (§7) before touching code — that decides how much of the rest of this section applies.

4.2 Develop#

  • Cut a branch off master; aspire to never commit directly to master.
  • nix develop gives the pinned toolchain; treefmt and pre-commit hooks (git-hooks.nix) format and lint on commit.
  • just preflight locally before pushing when the change is non-trivial.
  • Commits are conventional-ish and signed where the signing key is available (see the known travel/hardware-key caveats in project memory).

4.3 Verify (CI is the reviewer I don’t have)#

Two-speed gating, by design (see .github/workflows/):

Gate Workflow Trigger What it does
Fast eval eval.yml every branch push nix flake check --no-build — catches eval errors, dangling refs, bad option refs. Cheap, quick feedback.
Full build checks.yml PR to master and the master push after merge Full nix flake check that builds: the compliance VM test, per-host nginx -t, all host toplevels. Also re-runs on the merge commit to catch two-green-PRs-merge-red.
CVE / SBOM cve.yml manual (workflow_dispatch) vulnix scan + sbomnix SBOM; flags CVSS > 7.0 findings. Currently manual — see §6.
Docs docs.yml master push touching docs/modules/lib Builds .#docs, publishes to docs.falseblue.com (Cloudflare Pages).

4.4 Review & Merge#

  • Open a PR into master. Even solo, the PR is the record: the diff, the CI result, and my reasoning in one place.
  • Run the self-review checklist honestly.
  • Merge only when the full build gate is green. Squash-merge to keep master linear.

4.5 Release#

  • A push to master that changes flake.lock triggers release.yml: it cuts a CalVer tag (vYYYY.MM.DD[.n]) and fast-forwards the release branch.
  • release is the moving pointer the fleet follows — not every master commit ships automatically; the release branch is the deliberate ship line.

4.6 Deploy / Converge#

  • Hosts follow release via system.autoUpgrade; interactive/targeted pushes use deploy-rs.
  • Stage family-critical changes: prove on a low-stakes host (e.g. mokou) first, then converge the rest.
  • Respect the change window. Announce anything that could interrupt Wi-Fi, DNS, or Home Assistant.

4.7 Operate & Monitor#

  • Telemetry (VictoriaMetrics/Logs + Grafana + Alloy) and the public status pages watch availability.
  • Runbooks (runbooks/) and the emergency guide cover the known failure modes and SPOFs (e.g. the HA Green triple-SPOF).

4.8 Rollback#

  • Every deploy is a Nix generation: nixos-rebuild --rollback / boot the previous generation is the first move.
  • Reverting the release pointer or the offending PR restores the fleet’s target.
  • Rollback is expected and cheap, not a failure event. Reach for it early.

5. Roles (honest about being one person)#

Role Who How the conflict is managed
Developer Ben
Reviewer / Approver Ben Automation is the second set of eyes. No merge without green CI; the self-review checklist is mandatory, not optional.
Operator Ben Staged rollout + rollback-by-default limit operator error.
Users / stakeholders The family Their experience is the acceptance test. “Take a photo and text Ben” is the highest level incident-report channel.

Named limitation: no segregation of duties

A solo practice cannot separate author from approver. I don’t paper over it. The compensating controls are: deterministic builds, mandatory automated gates, staged rollout, trivially cheap rollback, and everything-as-code so any change is auditable after the fact.


6. Minimal Policies#

The smallest set worth writing down. Each is intent → control → evidence.

6.1 Change Management#

  • Intent: no unproven change reaches the family’s systems.
  • Control: branch → PR → green full-build gate → squash-merge → release tag. Direct pushes to master are (almost) never done.
  • Evidence: PR history, CI run logs (artifacts retained 30 days), CalVer tags.

6.2 Secrets Management#

  • Intent: no plaintext secret in the repo or the Nix store.
  • Control: sops-nix with age keys; policy in .sops.yaml. Secrets decrypt to /run/secrets (never /var/run/* — that path taint kills networking; see project memory).
  • Evidence: secrets.yaml is ciphertext; .sops.yaml recipient list; per-host key rotation via just sops-updatekeys.

6.3 Vulnerability Management#

  • Intent: know what’s in the closure and whether it’s vulnerable.
  • Control: cve.yml runs vulnix + sbomnix; findings with CVSS > 7.0 are surfaced. Accepted findings go in whitelist.toml with a written justification — an exception must be reasoned, not silent.
  • Evidence: vulnix.json, critical_cves.json, sbom.json, whitelist.toml.
  • Known gap

    cve.yml is currently manual (workflow_dispatch); the push/PR triggers are commented out. Re-enabling scheduled scanning is a tracked improvement, not the current state. Don’t claim continuous scanning until it’s on.

6.4 Compliance-as-Code#

  • Intent: security controls are enforced by the build, not by good intentions.
  • Control: the SCF-driven compliance layer (modules/compliance/) applies Secure Controls Framework controls as NixOS modules and attests them inside a NixOS VM test that runs in the full-build gate. Managed via the scf-add-control, scf-generate, and scf-check workflows.
  • Evidence: the compliance.evidence attestation trail and the rendered attestation report. See Compliance.

6.5 Access Control & Third-Party Inputs#

  • Intent: least privilege for both people and machine-to-machine trust.
  • Control: service exposure is Tailscale / Cloudflare-Access gated; the private sc2in inputs (corppraxis, PolicyPress) are fetched over git+ssh using a dedicated machine-account key (SC2_CI_SSH_KEY), not a personal credential.
  • Evidence: CI SSH setup steps; registry wiring in modules/flake/nix/.

6.6 Backup & Recovery#

  • Intent: the family’s data and the ability to rebuild survive a host loss.
  • Control: Borg/BorgWarehouse backups; Nix makes the systems reproducible so recovery is rebuild-plus-restore-data, not reinstall-from-memory.
  • Evidence: backup schedules and repos documented in maintenance and the runbooks; retention noted in project memory.

6.7 Documentation#

  • Intent: anyone in the family can act without me; future-me can understand past-me.
  • Control: docs live in-repo and auto-publish to docs.falseblue.com; the family-facing guides are written for non-technical readers.
  • Evidence: this docs site; docs.yml.

6.8 Licensing & Non-Commercial Use#

  • Intent: the license reflects that this is personal, non-commercial research.
  • Control: Apache 2.0, no warranty, commercial users assume all liability (see README).
  • Evidence: LICENSE, README.md.

7. Change Tiers#

Proportionality made concrete. Tier the change before you start.

Tier Examples Required rigor
T1 — Family-critical DNS, DHCP, Wi-Fi/RADIUS, Home Assistant, Paperless/documents, PKI/CA, the exit node, backups Full gate + compliance VM green; stage on a non-critical host first; announce; deploy in a change window; rollback plan ready.
T2 — Household service Jellyfin, Grafana, dashboards, scanners, non-critical containers Full gate green; deploy off-window; watch telemetry after.
T3 — Personal / cosmetic my desktop theme, shell, editor, dev tooling, docs wording Fast eval green is enough; deploy anytime to my own machines.

When unsure which tier, round up.


8. Self-Review Checklist#

Run before merging anything above T3:

  • Change tier identified; rigor matches (§7).
  • Fast eval gate green; for T1/T2, full build gate green (compliance VM included where applicable).
  • No new plaintext secret; anything sensitive went through sops.
  • Blast radius understood — what breaks for the family if this is wrong?
  • Are you pushing to master (eg will it affect any non-tsunami users if a rebuild is triggered)?
  • Rollback path is clear (which generation / which revert).
  • SPOFs and known-fragile areas (HA Green, exit node, octopi Wi-Fi) considered.
  • Docs / runbooks updated if behavior the family sees changed.
  • Deploy timing respects the change window.

9. Records & Evidence#

Where the audit trail actually lives — no separate GRC tool needed:

  • Git history & PRs — every change, its reasoning, and its review.
  • CI artifacts — flake-check / eval logs (30 days), CVE/SBOM results (90 days).
  • CalVer tags & release branch — what shipped and when.
  • Compliance attestationcompliance.evidence + rendered report.
  • Project memory — the non-obvious operational context between sessions.

10. Review Cadence#

This is a living document. Review it:

  • Quarterly, lightly — does it still describe what I actually do?
  • On any material change to CI, the release flow, the secrets model, or the compliance layer.
  • After any family-impacting incident — fold the lesson into a policy or a runbook so it can’t recur silently.

Last aligned with the repo: 2026-07-21.