# parity-lib Shared per-archetype **publish-app builders** for the ~51 "parity" repos (cluster #192/#193/#194, emmett#44). Instead of each repo hand-inlining its own `stage` / `publish` / `push-staged` shell, every repo consumes ONE versioned source of truth: this flake's `lib.mk*Publish` builders generate the flake apps that implement the corrected parity standard. ## The corrected parity standard (emmett#44) Parity has two halves, and only one of them can be cluster-independent: - **BUILD/STAGE parity is cluster-independent.** `nix run .#stage-` cross-builds the artifact into a local on-disk store (`./.parity-stage/` by convention) and makes **NO registry contact**, so it runs identically on emmett even when armer / the registry is down. - **PUBLISH parity shares fate with the cluster** (the only registry is co-located with it). The publish apps name the blocker (`registry-down`) up front and never half-push. Every push entrypoint **DRY-RUNS by default** — it stages and prints what it WOULD push. You must pass `--publish` (or set `PUBLISH=1`) to mutate the registry, so an accidental local run can never push. ## App shape each builder emits | app | parity half | registry | mutates? | | --- | --- | --- | --- | | `stage-` | BUILD | none | no | | `publish-` | BUILD + PUBLISH | one arch | only with `--publish` | | `publish-index` | PUBLISH | multi-arch assembly (regctl) | only with `--publish` | | `publish` | BUILD + PUBLISH | all local arches + index + `:latest` | only with `--publish` | | `push-staged` | PUBLISH | replay `.parity-stage` | only with `--publish` | - `publish-index` is **build-free**: it assembles `:` from the per-arch digest-pinned tags via `regctl index create`, and is **fail-closed** — if a required arch was not pushed this run, it refuses to assemble a partial index. - `publish` runs all locally-buildable arches, then the index, then copies `:` → `:latest` as the **LAST** (single, idempotent) mutation. - `push-staged` replays artifacts from `./.parity-stage` to the registry, for when the cluster was down at build time. Single-arch archetypes (PyPI wheel, npm addon, generic/Go binary, Helm chart) have no multi-arch index, so they expose `stage-` / `publish-` / `publish` / `push-staged` only. The nix2container (OCI) builder is the one that yields the full `publish-index` / `:latest` set. ## Shared building blocks (also exposed) All builders source one shell library (`ci/parity-lib.sh`, materialized in the Nix store) so behavior cannot drift between repos: - **Token resolution** — `$REGISTRY_TOKEN` → `pass infra/gitea/...` fallback → named hard-fail. The token is **never printed** and scripts run under `set -euo pipefail` only (never `set -x`). - **Dev-tag guard** — refuses a real (`:latest`/release) publish unless `$VERSION` is set or `$CI_COMMIT_TAG` is a `v*` tag. - **Version derivation** — `$VERSION` → strip leading `v` + trailing `-N` from `$CI_COMMIT_TAG` → the flake's pinned default. Identical for CI and local. ## API — `lib.*` `lib` is system-independent. Two ways to consume it: ```nix # (a) one call, all builders, with your own pkgs: parity.lib.mkParityBuilders pkgs # -> { mkPyPiWheelPublish, ... } # (b) per-builder wrapper that takes pkgs as the first argument: parity.lib.mkPyPiWheelPublish pkgs { pname = "..."; version = "..."; ... } ``` Exposed attrs: - `lib.mkParityBuilders` — `pkgs -> { the six builders + mkApp + shellLib }` - `lib.mkPyPiWheelPublish` — `pkgs -> args -> { apps }` - `lib.mkS390xNpmPublish` — `pkgs -> args -> { apps }` - `lib.mkGenericBinaryPublish` — `pkgs -> args -> { apps }` - `lib.mkNix2ContainerPublish` — `pkgs -> args -> { apps }` - `lib.mkGoBinaryPublish` — `pkgs -> args -> { apps }` (alias of generic-binary) - `lib.mkHelmPublish` — `pkgs -> args -> { apps }` ### Builder arguments ```nix mkPyPiWheelPublish { pname = "asyncpg"; version = "0.31.0"; # default; overridden by $VERSION / $CI_COMMIT_TAG wheel = self.packages.x86_64-linux.default; # drv/dir to glob *.whl, or a .whl path arch = "s390x"; # default } mkS390xNpmPublish { pname = "@rollup/rollup-linux-s390x-gnu"; version = "4.0.0"; nodeFile = self.packages.x86_64-linux.addon; # the *.node nodeFileName = "rollup.linux-s390x-gnu.node"; packageJson = ''{ "name": "@rollup/...", "version": "$VERSION", ... }''; } mkGenericBinaryPublish { # mkGoBinaryPublish is an alias pname = "geesefs"; version = "0.43.5"; binary = "${self.packages.x86_64-linux.default}/bin/geesefs"; assetName = "geesefs-linux-s390x"; arch = "s390x"; } mkNix2ContainerPublish { imageName = "git.oleks.space/oleks/nix-ci"; version = "1.2.3"; images = { # only arches buildable on THIS host amd64 = { copyTo = self.packages.x86_64-linux.nix-ci.copyTo; }; }; arches = [ "amd64" "arm64" ]; # the full set the index must cover (fail-closed) } mkHelmPublish { pname = "firecrawl"; version = "0.1.3"; chartSrc = ./charts/firecrawl; ociRepo = "oci://git.oleks.space/oleks"; # default } ``` Common optional args on every builder: `registryHost` (`git.oleks.space`), `registryOwner` (`oleks`), `passEntry` (`infra/gitea/personal_access_token_packages_rw`). ### Consuming it in a parity repo's flake ```nix { inputs.parity.url = "git+https://git.oleks.space/oleks/parity-lib"; outputs = { self, nixpkgs, parity, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; in { apps = parity.lib.mkGenericBinaryPublish pkgs { pname = "geesefs"; version = "0.43.5"; binary = "${self.packages.${system}.default}/bin/geesefs"; assetName = "geesefs-linux-s390x"; }; }); } ``` `.woodpecker.yaml` stays thin — it runs the identical app, so CI and local cannot drift: ```yaml commands: - PUBLISH=1 nix run .#publish ``` ## pipeline-doctor (cluster #193) `nix run .#pipeline-doctor -- ` asserts the parity contract on a repo: archetype declared, consumes parity-lib, token = `$REGISTRY_TOKEN` + `pass` fallback and never printed, dev-tag guard present, a `--dry-run` default exists, apps carry `meta.description`, and an enumerable `publish-*` / `stage-*` naming. It prints the local-equivalent commands. It is read-only (no token, no registry contact) and exits non-zero if any required check fails. ## Verifying changes locally ```bash nix eval .#lib --apply builtins.attrNames --json nix flake show nix build .#pipeline-doctor shellcheck ci/*.sh statix check . markdownlint-cli2 README.md ``` The git.oleks.space server runs a pre-receive linter; the commands above mirror it.