#!/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 [] (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-/publish- 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- # BUILD-parity: stage to .parity-stage, no registry" echo " nix run .#publish- # 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 ]