a56d219418
impureBuild: build the consumer's flake attr at run time via nix build --impure --sandbox false, instead of embedding the image closure as an eval-time build dep — required for images that fetch private artifacts with a token at build time (ii-agent). indexMovesLatest: publish-index also moves :latest, for repos that publish each arch on a separate CI agent and converge in a final index-only step. Both opt-in, default-off; existing consumers unchanged. Verified by eval in both modes.
170 lines
12 KiB
Markdown
170 lines
12 KiB
Markdown
# Changelog
|
|
|
|
<!-- markdownlint-disable MD013 -->
|
|
|
|
All notable changes to parity-lib are documented here. This project follows
|
|
semantic versioning; the version is a conceptual tag (no git tag is created).
|
|
|
|
## Unreleased
|
|
|
|
- **Feature: `mkNix2ContainerPublish` impure-build + index-moves-latest modes
|
|
(cluster #205).** Two opt-in args, both default-off so every existing consumer
|
|
is byte-unchanged:
|
|
- `impureBuild ? false` — for images that are built **impurely** (need a
|
|
registry token / network / `--option sandbox false` at build time, e.g.
|
|
ii-agent fetching private wheels). The default contract references
|
|
`images.<arch>.copyTo` as an **eval-time store path** — a build dep of the
|
|
publish app — which is fatal for an impure image (it would build under the
|
|
pure sandbox). In impure mode each `images.<arch>` instead carries `attr`
|
|
(a flake attribute on the *consumer's own* flake), and `stage`/`verify-digest`
|
|
build it at **run time** via `nix build --impure --option sandbox false
|
|
".#<attr>.copyTo"`, then read the path back; `publish` reads the staged
|
|
copy-to path (no rebuild). The impure closure never becomes a build dep of
|
|
the app.
|
|
- `indexMovesLatest ? false` — when true, `publish-index` ALSO moves `:latest`
|
|
(a dev-guarded digest copy of `:VERSION`) after assembling the index. For
|
|
repos that publish each arch on a **separate CI agent** and converge in a
|
|
final index-only step (ii-agent's `.woodpecker/{amd64,arm64}.yaml` +
|
|
`publish-index`), `:latest` must move there rather than in `publish`.
|
|
|
|
Verified by eval (both modes): impure `stage`/`verify-digest` emit `nix build
|
|
--impure`, `publish` delegates to the staged path, `publish-index` moves
|
|
`:latest`; pure mode emits the store path, no impure build, and `publish-index`
|
|
does NOT touch `:latest`.
|
|
- **Fix: `pipeline-doctor` models branch-deploy repos (#204).** The dev-tag-guard
|
|
check only accepted a `refs/tags/v*` tag gate, so web-app/CMS repos that deploy
|
|
on a **branch push** (`event: push` + `branch: develop/staging/production`) and
|
|
tag each push from `CI_COMMIT_BRANCH`/`CI_COMMIT_SHA`/`CI_PIPELINE_NUMBER` (a
|
|
deterministic per-push tag, no default-version clobber) false-failed. The check
|
|
now accepts that branch-deploy form as a valid guard. cms-plugins,
|
|
emdash-kotkanagrilli, kotkanagrilli.fi → 9/9; trio + self-test unchanged; a
|
|
tagless `event: push` publish with NO per-push tag still FAILs.
|
|
- **Fix: `pipeline-doctor` now reads a `.woodpecker/` DIRECTORY (#202).** It only
|
|
folded the single-file `.woodpecker.yaml`/`.yml` into `ci_txt`; repos whose
|
|
Woodpecker config is a `.woodpecker/` directory (per-arch workflows) had their
|
|
`refs/tags/v*` trigger invisible, so the dev-tag-guard check false-failed.
|
|
Now globs `.woodpecker/*.yaml|*.yml` too. Effect: commonground-legacy + csi-s3
|
|
go 9/9. (cms-plugins still 8/9 — but legitimately: it's a *branch-deploy* repo
|
|
with no `v*` tag at all, a separate heuristic gap tracked elsewhere.)
|
|
- **Fix: `pipeline-doctor` token-heuristic false positives (#199).** The audit
|
|
sweep wrongly FAILED ~9 correctly-converted ci-script repos on two heuristics.
|
|
(1) The token-contract check only accepted a hard-coded `pass <literal-path>`;
|
|
it now also accepts the secure indirection `pass "$PASS_ENTRY"` / `pass "$VAR"`
|
|
(a quoted/unquoted shell var, optionally via `pass show`). (2) The leak scan
|
|
flagged the blessed `echo "$TOKEN" | docker login … --password-stdin` (and
|
|
`--pass-stdin`, and helm `registry login`) idiom — the token goes to STDIN, not
|
|
the log — because the `--password-stdin` flag often sits on a `\`-wrapped
|
|
continuation line the line-based grep never saw on the `echo` line. The scan now
|
|
flattens `\`-continuations and folds the pipe target onto the `echo` line, then
|
|
exempts the `… | <cmd> … --password-stdin`/`--pass-stdin` feed. Real leaks still
|
|
FAIL: a bare `echo "$TOKEN"` to stdout or a file, and `set -x` in a token script.
|
|
Added `--self-test`: six inline fixtures lock in both fixes and the three
|
|
must-still-catch leaks. Verified: version-radar, xonsh, common-chronicle,
|
|
ii-researcher, ironclaw, openclaw now PASS; parity-lib `--strict .`, gitea-mcp,
|
|
numpy-s390x still 9/9. (commonground-legacy / cms-plugins / csi-s3 still FAIL one
|
|
UNRELATED check — dev-tag-guard — because their woodpecker config lives in a
|
|
`.woodpecker/` directory the doctor doesn't yet read; out of scope for #199.)
|
|
- **Feature: `mkAtticClosurePublish` — the attic-closure builder (cluster #198).**
|
|
Models the archetype parity-lib was missing: build a Nix closure and push it to
|
|
the Attic binary cache (NO registry artifact). Yields `stage-<arch>` (`nix build`
|
|
the closure, no push), `publish-<arch>`/`publish` (build + `attic login` + `attic
|
|
push`; dry-run by default, `--publish`/`PUBLISH=1` to push; token via `$ATTIC_TOKEN`
|
|
or `pass`, never echoed), and `push-staged`. Lets caddy-with-replace (#104) drop its
|
|
generic-publish over-reach and overlay-xonsh (#105) convert off N/A; flake-hub /
|
|
woodpecker-peek can retire their bespoke attic wraps.
|
|
- **`pipeline-doctor` non-flake mode (cluster #191/#193).** A repo with NO root
|
|
`flake.nix` is now a VALID parity form if it ships the ci-script entrypoints
|
|
(`ci/local.sh`, or `ci/build.sh` + `ci/publish.sh`) called by a thin
|
|
`.woodpecker.yaml` — so the non-flake go-binary/helm references (gitea-mcp, helms)
|
|
PASS the gate instead of failing for lacking a flake. The token-leak scan and the
|
|
#191 no-`set -x`-in-token-scripts scan still run on their `ci/*.sh` in full.
|
|
Verified: gitea-mcp now 9/9, parity-lib + numpy-s390x still 9/9.
|
|
- **Feature: `verify-digest` for nix2container (cluster #195).** `mkNix2ContainerPublish`
|
|
now also returns a `verify-digest` app that builds each locally-buildable arch
|
|
image and prints its OCI **manifest digest** with NO registry contact (it
|
|
`copyTo`s a throwaway local `oci:` dir and reads the digest skopeo derives).
|
|
This formalizes the manifest digest as the parity contract: the OCI layers are
|
|
built `reproducible = false` ON PURPOSE (the fix for the "Digest did not match"
|
|
caused by non-reproducible layer deps + nix2container's lazy tar regeneration),
|
|
so byte-identical-tar parity is NOT promised — but the content-addressed
|
|
manifest digest the registry stores the image under IS stable. Identical local
|
|
vs CI digest ⇒ identical registry artifact. We do NOT flip `reproducible` to
|
|
`true` (the inputs are not reproducible); the LOW-RISK digest-as-contract path
|
|
was chosen instead. Generalized from the claude-plugin-registry prototype
|
|
(`55f2d0b`) so every nix2container consumer gets it for free.
|
|
- **`pipeline-doctor` is now GATE-READY (cluster #191/#193).** It already exited
|
|
non-zero on any failing required check; added a `--strict` mode that ALSO fails
|
|
on any `WARN`, so a `.woodpecker.yaml` step or a server pre-receive hook can call
|
|
`pipeline-doctor --strict <repo>` and rely on the exit code. Added a documented
|
|
**`ci/local.sh` escape-hatch** (cluster #196): a repo that must keep a
|
|
hand-written Dockerfile/BuildKit pipeline may opt out of the archetype /
|
|
parity-lib asserts (downgraded to warnings) if it ships a `ci/local.sh`
|
|
local==CI entrypoint. Fixed a false-negative: the token / dev-tag / dry-run /
|
|
`meta.description` contracts are GUARANTEED by parity-lib for a consumer (they
|
|
live in the generated apps, not the consumer's `flake.nix` text), so a repo that
|
|
consumes parity-lib now PASSES those by delegation instead of being penalized
|
|
for not re-implementing them inline. Self-check stays green and a known-good
|
|
consumer (`numpy-s390x`) now passes 9/9.
|
|
- **Audit: stage + push-staged uniform across all 8 builders (cluster #194).**
|
|
Verified every archetype builder exposes a `stage-<arch>` (build-parity, no
|
|
registry contact, writes `./.parity-stage`) AND a `push-staged` (replay the
|
|
staged artifact): `mkPyPiWheelPublish`, `mkPyPiWheelPublishMulti`,
|
|
`mkS390xNpmPublish`, `mkS390xNpmPublishMulti`, `mkGenericBinaryPublish`,
|
|
`mkGoBinaryPublish` (alias), `mkNix2ContainerPublish` and `mkHelmPublish`
|
|
(`stage-chart`). All were already complete — no gaps to fill; the build-parity /
|
|
publish-parity split is uniform.
|
|
- **Feature: `mkS390xNpmPublishMulti` (cluster #192).** A multi-version npm
|
|
builder mirroring the PyPI multi one: publishes a fixed list of
|
|
`{ version; file; distTag? }` per tag, each staged into its own dir and
|
|
`npm publish`ed with its dist-tag (idempotent — "already exists" == success).
|
|
`file` may be a `.node` addon OR a plain binary, and `packageJson` (with a
|
|
`$VERSION` the stage heredoc expands) declares the shape (`main` vs `bin`), so
|
|
it covers both nextjs-swc (16.1.6 `@latest` + 15.2.0 `@next15`) and sentry-cli
|
|
(a binary published as an npm package at two versions). Shared
|
|
`parity_npm_publish_dir` helper added to `ci/parity-lib.sh`.
|
|
- **Feature: `mkPyPiWheelPublishMulti` (cluster #197).** A multi-version PyPI
|
|
builder that publishes a fixed list of `{ version; wheel; }` per tag instead of
|
|
just the default — the pre-parity behaviour several `*-s390x` repos rely on.
|
|
Each wheel's real version is read from its filename (PEP 427), so
|
|
stage/publish/push-staged need no side-channel map and a re-run is idempotent
|
|
(409-skip per version). Shared `parity_pypi_post` / `parity_wheel_version`
|
|
helpers added to `ci/parity-lib.sh`. First consumer: `numpy-s390x` (5 versions).
|
|
- **Fix (safety): dev-tag guard was ineffective.** Every publish app body runs
|
|
`VERSION="$(parity_derive_version <default>)"` before `parity_devtag_guard`, so
|
|
by the time the guard checked `$VERSION` it was always non-empty (the derived
|
|
default) and an accidental local `--publish` with no explicit version and no
|
|
`v*` tag still pushed (cluster #194 finding). The guard now reads a source-time
|
|
snapshot `PARITY_VERSION_EXPLICIT` captured before any clobber, so it correctly
|
|
blocks unless the caller set `$VERSION` or `$CI_COMMIT_TAG` matches `^v[0-9]`.
|
|
- `pipeline-doctor` (cluster #191 security sweep): added a scoped per-file check
|
|
asserting **no `set -x` in token-bearing `ci/*.sh` scripts** going forward — a
|
|
script that references a registry token (`REGISTRY_TOKEN` / `CI_REGISTRY_TOKEN`
|
|
/ an `Authorization: token` header) must not enable xtrace, which would echo
|
|
the token to the build log. Token-free helpers (e.g. version parsers) are not
|
|
flagged.
|
|
|
|
## v0.1.0
|
|
|
|
Initial release (cluster #192/#193/#194, emmett#44).
|
|
|
|
- `lib.mkParityBuilders pkgs` plus per-builder wrappers exposing the six
|
|
archetype publish-app builders:
|
|
- `mkPyPiWheelPublish` — single-arch Gitea PyPI wheel.
|
|
- `mkS390xNpmPublish` — single-arch Gitea npm native addon.
|
|
- `mkGenericBinaryPublish` — single-arch Gitea generic-registry binary.
|
|
- `mkGoBinaryPublish` — alias of `mkGenericBinaryPublish` (explicit archetype).
|
|
- `mkNix2ContainerPublish` — multi-arch OCI image with `publish-index` and
|
|
`:latest` digest copy.
|
|
- `mkHelmPublish` — Helm chart to an OCI registry.
|
|
- Each builder returns flake apps following the corrected parity standard:
|
|
`stage-<arch>` (build-parity, no registry), `publish-<arch>` (dry-run by
|
|
default), `publish-index` (build-free, fail-closed multi-arch assembly via
|
|
regctl), `publish` (all local arches + index + `:latest` last), and
|
|
`push-staged` (replay `./.parity-stage`).
|
|
- Shared shell library `ci/parity-lib.sh` (token resolution with
|
|
`$REGISTRY_TOKEN` + `pass` fallback and never printed, dev-tag guard, version
|
|
derivation, the dry-run gate, registry preflight, stage-dir helpers).
|
|
- `packages.pipeline-doctor` / `apps.pipeline-doctor` (cluster #193): static
|
|
parity-contract checker that prints local-equivalent commands.
|
|
- `flake.lock` fully pinned; nixpkgs follows the shared `fleet-pins` `nixpkgs-ci`.
|