Files
parity-lib/ci/pipeline-doctor.sh
T
Oleks 2201257e89 feat: shared per-archetype parity publish-app builders (v0.1.0)
Implements the shared parity flake-module library so the ~51 parity repos
consume one source of truth instead of hand-inlined publish shells.

- lib.mk{PyPiWheel,S390xNpm,GenericBinary,Nix2Container,GoBinary,Helm}Publish
  builders returning stage-<arch>/publish-<arch>/publish-index/publish/
  push-staged apps per the corrected emmett#44 standard (build-parity stages to
  ./.parity-stage with no registry contact; publish dry-runs by default;
  publish-index is build-free + fail-closed; :latest is the last digest copy).
- Shared ci/parity-lib.sh: token resolution ($REGISTRY_TOKEN + pass fallback,
  never printed), dev-tag guard, version derivation, dry-run gate, preflight.
- pipeline-doctor package/app asserting the parity contract (cluster #193).

Refs cluster #192, #193, #194, emmett#44.
2026-06-02 04:15:48 +03:00

132 lines
5.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# pipeline-doctor (cluster #193): assert the parity contract for a repo.
#
# Given a repo path, it statically checks that the repo follows the corrected
# emmett#44 parity standard, and prints the local-equivalent commands a dev can
# run. It is read-only: it never touches the registry and never needs a token.
#
# Usage: pipeline-doctor [<repo-path>] (default: .)
# Exit: 0 = all required checks pass; 1 = one or more required checks failed.
set -euo pipefail
REPO="${1:-.}"
REPO="$(cd "$REPO" && pwd)"
FLAKE="$REPO/flake.nix"
fail=0
pass_n=0
note() { printf ' %s %s\n' "$1" "$2"; }
ok() {
pass_n=$((pass_n + 1))
note "PASS" "$1"
}
bad() {
fail=$((fail + 1))
note "FAIL" "$1"
}
warn() { note "WARN" "$1"; }
echo "pipeline-doctor: $REPO"
if [ ! -f "$FLAKE" ]; then
echo "FAIL: no flake.nix at $REPO" >&2
exit 1
fi
flake_txt="$(cat "$FLAKE")"
# 1. archetype declared (a publish/stage app whose name encodes the arch, or an
# explicit archetype marker comment).
if printf '%s' "$flake_txt" | grep -Eq 'archetype|stage-[a-z0-9_]+|publish-[a-z0-9_]+'; then
ok "archetype declared (stage-*/publish-* app or archetype marker present)"
else
bad "no archetype: expected a stage-<arch>/publish-<arch> app or 'archetype' marker"
fi
# 2. consumes parity-lib (the shared module).
if printf '%s' "$flake_txt" | grep -Eq 'parity-lib|parity\.lib|mk(PyPiWheel|S390xNpm|GenericBinary|Nix2Container|GoBinary|Helm)Publish'; then
ok "consumes parity-lib (input or one of the mk*Publish builders)"
else
bad "does not consume parity-lib: inline the input and use a mk*Publish builder"
fi
# 3. token = $REGISTRY_TOKEN with pass fallback, and never printed.
token_src="$flake_txt"
if [ -d "$REPO/ci" ]; then
token_src="$token_src
$(cat "$REPO"/ci/*.sh 2>/dev/null || true)"
fi
if printf '%s' "$token_src" | grep -Eq 'REGISTRY_TOKEN' &&
printf '%s' "$token_src" | grep -Eq 'parity_resolve_token|pass (show )?infra/gitea/personal_access_token_packages_rw'; then
ok "token = \$REGISTRY_TOKEN with pass fallback"
else
bad "token contract missing: need \$REGISTRY_TOKEN + pass fallback (parity_resolve_token)"
fi
# token never printed: flag an obvious leak. A redacting pipe (sed s/$tok/.../)
# is fine, so only flag a bare echo/printf of the token NOT piped to a redactor,
# or an enabled `set -x` (which would trace the token). The leak pattern uses a
# literal '$' built via a variable so this lib stays single-quote clean (SC2016).
dollar='[$]'
# Anchor on echo/printf used as a COMMAND (line-leading, after `;`, `|`, `&&`,
# `(` or `then/do/else`) so the doctor doesn't flag the regex literals it
# carries inside string assignments.
leak_pat="(^|[;|&(]|then |do |else )[[:space:]]*(echo|printf)[^|]*${dollar}(REGISTRY_TOKEN|TOKEN|tok|token)([^A-Za-z0-9_]|$)"
leak=0
if printf '%s' "$token_src" | grep -Eq "^[[:space:]]*set -x([[:space:]]|$)"; then leak=1; fi
# Exclude: redactions, stdin-feeds, the doctor's own $token_src var, an escaped
# '\$' (a token NAME in a message, not its value), and the sanctioned
# `printf '%s' "$TOKEN"` capture idiom (the resolver returns the token on stdout
# for a caller to capture — that is the one blessed place a token is emitted).
if printf '%s' "$token_src" |
grep -E "$leak_pat" |
grep -vE "REDACTED|sed |stdin|token_src" |
grep -vqE 'printf .%s. "[$](REGISTRY_TOKEN|TOKEN|tok|token)"|\\[$]'; then
leak=1
fi
if [ "$leak" -eq 0 ]; then
ok "no obvious token leak (token never bare-echoed; no set -x)"
else
bad "possible token leak: a token var is echo/printf'd un-redacted, or set -x is enabled"
fi
# 4. dev-tag guard present.
if printf '%s' "$token_src" | grep -Eq 'parity_devtag_guard|refusing to publish without an explicit'; then
ok "dev-tag guard present"
else
bad "no dev-tag guard: a default-version --publish could clobber the registry"
fi
# 5. a --dry-run default exists.
if printf '%s' "$token_src" | grep -Eq 'parity_parse_args|DRY-RUN|dry-run|PUBLISH:-0|PUBLISH=\$\{PUBLISH:-0\}'; then
ok "dry-run default exists (publish mutates only on --publish/PUBLISH=1)"
else
bad "no dry-run default: publish must NOT mutate the registry by default"
fi
# 6. apps carry meta.description.
if printf '%s' "$flake_txt" | grep -Eq 'meta\.description|meta = \{'; then
ok "apps carry meta.description"
else
bad "apps missing meta.description"
fi
# 7. enumerable publish-* naming.
pubs="$(printf '%s' "$flake_txt" | grep -oE '(stage|publish)(-[a-z0-9_]+)?' | sort -u || true)"
if [ -n "$pubs" ]; then
ok "enumerable publish-* / stage-* app naming:"
printf '%s\n' "$pubs" | sed 's/^/ /'
else
bad "no enumerable publish-*/stage-* apps found"
fi
echo ""
echo "Local-equivalent commands (what CI runs is identical):"
echo " nix run .#stage-<arch> # BUILD-parity: stage to .parity-stage, no registry"
echo " nix run .#publish-<arch> # stage + push that arch (DRY-RUN; add --publish)"
echo " nix run .#publish-index # build-free multi-arch assembly from pushed digests"
echo " nix run .#publish # all local arches + index; :latest = digest copy of :TAG"
echo " nix run .#push-staged # replay .parity-stage to the registry (cluster-was-down)"
echo ""
echo "Summary: $pass_n passed, $fail failed."
[ "$fail" -eq 0 ]