diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 6b3973d..75883f8 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -131,8 +131,3 @@ jobs: exit 1 fi echo "All action refs are SHA-pinned." - - # Phone artifact on green. Runs on the ArchDev build node (host mode, label - # archdev) where the aarch64 sysroot lives — no docker, no node, plain shell. - # Publishes the cross-built binary to a rolling `edge` prerelease; the phone - # updates itself from there instead of waiting on a laptop scp. diff --git a/.gitea/workflows/packages.yml b/.gitea/workflows/packages.yml deleted file mode 100644 index 8647612..0000000 --- a/.gitea/workflows/packages.yml +++ /dev/null @@ -1,456 +0,0 @@ -name: packages - -# Internal only. Packaging, signing and archive publication run on the -# build host where the aarch64 sysroot and the signing key live. This file -# is not carried on the public branch; the gates in ci.yml are. -on: - push: - branches: [primary, main] - workflow_dispatch: - -jobs: - aarch64-artifact: - runs-on: archdev - needs: [rust-test, no-ai-attribution, action-pins] - if: github.event_name == 'push' && github.ref == 'refs/heads/primary' - steps: - - name: Clone repo + pinned tuie - run: | - set -euo pipefail - rm -rf "$GITHUB_WORKSPACE/src" - git -c http.extraheader="Authorization: token ${{ secrets.LOCAL_GITEA_TOKEN }}" \ - clone "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" "$GITHUB_WORKSPACE/src/Souveraine" - git -C "$GITHUB_WORKSPACE/src/Souveraine" checkout "$GITHUB_SHA" - git -c http.extraheader="Authorization: token ${{ secrets.LOCAL_GITEA_TOKEN }}" \ - clone "${GITHUB_SERVER_URL}/Fimeg/tuie.git" "$GITHUB_WORKSPACE/src/tuie" - git -C "$GITHUB_WORKSPACE/src/tuie" checkout aa43e335c8f5a3cd2c78e397a1a8dfe534a6bc35 - - - name: Build release binaries - run: | - set -euo pipefail - cd "$GITHUB_WORKSPACE/src/Souveraine" - # Persistent target dirs: fresh clones per run, warm compile cache. - export CARGO_TARGET_DIR="$HOME/.cache/souveraine-ci-target" - export SOUVERAINE_AARCH64_SYSROOT="$HOME/aarch64-sysroot" - ./scripts/build-cross.sh - BIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine" - file "$BIN" | grep -q aarch64 || { echo "built binary is not aarch64" >&2; exit 1; } - cp "$BIN" souveraine-aarch64 - - # sessiond is a separate bin behind the sessiond feature. It was - # hand-copied to the phone's /usr/local/bin and owned by no package, - # so it never received updates; build and ship it with the rest. - ./scripts/build-cross.sh --features sessiond --bin souveraine-sessiond - SBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-sessiond" - file "$SBIN" | grep -q aarch64 || { echo "sessiond is not aarch64" >&2; exit 1; } - cp "$SBIN" souveraine-sessiond-aarch64 - - # The sensor reporter. Same story as sessiond: it replaces shell - # scripts that shipped in rootfs-overlay/ and drifted per-device. - ./scripts/build-cross.sh --bin souveraine-sensord - NBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-sensord" - file "$NBIN" | grep -q aarch64 || { echo "sensord is not aarch64" >&2; exit 1; } - cp "$NBIN" souveraine-sensord-aarch64 - - # Full USB KVM receiver: GUD display-in and the HID return surface. - ./scripts/build-cross.sh --features usb-kvm --bin souveraine-usb-kvm - UBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-usb-kvm" - file "$UBIN" | grep -q aarch64 || { echo "usb-kvm is not aarch64" >&2; exit 1; } - cp "$UBIN" souveraine-usb-kvm-aarch64 - - # The wry host: self-hosted sites as apps, and the rig she wears. - # Needs webkit2gtk on the build sysroot; it is already installed on - # the phone (TASK-59 phase 1 measured against it). - # Best effort: it links against the system webview, so it needs - # webkit2gtk in the aarch64 sysroot. Until that is there this is the - # one binary that may legitimately not build, and it must not stop the - # ones that decide whether the phone locks. - if ./scripts/build-cross.sh --features web --bin souveraine-web; then - WBIN="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/souveraine-web" - if file "$WBIN" | grep -q aarch64; then - cp "$WBIN" souveraine-web-aarch64 - else - echo "::warning::web host built but is not aarch64; skipping it" - fi - else - echo "::warning::the wry host did not cross-build (webkit2gtk sysroot?); skipping it" - fi - - # secrets rail + machined: also hand-copied on the phone until now. - ./scripts/build-cross.sh --features secrets --bin souveraine-secrets - ./scripts/build-cross.sh --bin souveraine-machined - ./scripts/build-cross.sh --bin souveraine-admit - for b in souveraine-secrets souveraine-machined souveraine-admit; do - B="$CARGO_TARGET_DIR/aarch64-unknown-linux-gnu/release/$b" - file "$B" | grep -q aarch64 || { echo "$b is not aarch64" >&2; exit 1; } - cp "$B" "$b-aarch64" - done - - export CARGO_TARGET_DIR="$HOME/.cache/souveraine-ci-target-x86_64" - cargo build --release - BIN="$CARGO_TARGET_DIR/release/souveraine" - file "$BIN" | grep -q 'x86-64' || { echo "built binary is not x86_64" >&2; exit 1; } - cp "$BIN" souveraine-x86_64 - - cargo build --release --features secrets --bin souveraine-secrets - cargo build --release --bin souveraine-machined - cargo build --release --bin souveraine-admit - for b in souveraine-secrets souveraine-machined souveraine-admit; do - B="$CARGO_TARGET_DIR/release/$b" - file "$B" | grep -q 'x86-64' || { echo "$b is not x86_64" >&2; exit 1; } - cp "$B" "$b-x86_64" - done - - # sessiond for x86_64 as well. It was skipped here with a comment - # calling it deliberate; it never was (Casey, 2026-08-02) — the - # laptop hit lock-screen errors with it once and it stayed - # phone-only by inertia. That inertia is now load-bearing in the - # wrong direction: viewtop targets the laptop screen too - # (VIEWTOP-AND-DENIAL.md), and panel power, the LockedHint report and - # button reporting all assume the daemon is there — so no session - # authority on x86 means no viewtop on the laptop. - # - # Building and packaging are not enabling. The package-owned user - # unit ships on both architectures, but package installation neither - # enables nor starts it; target activation remains separately tested. - cargo build --release --features sessiond --bin souveraine-sessiond - B="$CARGO_TARGET_DIR/release/souveraine-sessiond" - file "$B" | grep -q 'x86-64' || { echo "sessiond is not x86_64" >&2; exit 1; } - cp "$B" souveraine-sessiond-x86_64 - - # sensord for x86_64 too. It stopped being phone-only when charge - # became a source: sessiond no longer probes the supplies itself, so - # a machine without the reporter has no charge evidence at all — and - # the laptop is a machine with a battery. - cargo build --release --bin souveraine-sensord - B="$CARGO_TARGET_DIR/release/souveraine-sensord" - file "$B" | grep -q 'x86-64' || { echo "sensord is not x86_64" >&2; exit 1; } - cp "$B" souveraine-sensord-x86_64 - - sha256sum souveraine-aarch64 souveraine-x86_64 \ - souveraine-sessiond-aarch64 souveraine-sensord-aarch64 \ - souveraine-usb-kvm-aarch64 \ - souveraine-secrets-aarch64 souveraine-secrets-x86_64 \ - souveraine-machined-aarch64 souveraine-machined-x86_64 \ - souveraine-admit-aarch64 souveraine-admit-x86_64 \ - souveraine-sessiond-x86_64 souveraine-sensord-x86_64 \ - > souveraine-binaries.sha256 - - - name: Build UPower fork (per-arch install trees) - run: | - set -euo pipefail - cd "$GITHUB_WORKSPACE/src/Souveraine" - # The fork is a git submodule of Souveraine. The runner checkout - # above is not recursive; clone it explicitly with the same LAN - # token used for tuie, pinned to the submodule's recorded commit. - UPOWER_URL=$(git config --file .gitmodules submodule."packaging/upower-souveraine".url) - UPOWER_SHA=$(git ls-tree HEAD packaging/upower-souveraine | awk '{print $3}') - git -c http.extraheader="Authorization: token ${{ secrets.LOCAL_GITEA_TOKEN }}" \ - clone "$UPOWER_URL" "$GITHUB_WORKSPACE/src/upower" - git -C "$GITHUB_WORKSPACE/src/upower" checkout "$UPOWER_SHA" - - SYSROOT="$HOME/aarch64-sysroot" - # .cargo/aarch64-pkg-config reads SOUVERAINE_AARCH64_SYSROOT and falls - # back to /usr/aarch64-linux-gnu, which has no glib. Steps do not share - # environment, so exporting it in the binaries step does not reach here. - export SOUVERAINE_AARCH64_SYSROOT="$SYSROOT" - - # --- x86_64: native build on the archdev host --- - ( - cd "$GITHUB_WORKSPACE/src/upower" - # gtkdoc-scan is not installed on the runner, so gtk-doc=true fails - # here too; man needs the same toolchain. Introspection is left ON - # for the native build — it works, and costs nothing. - meson setup build-x86_64 \ - --prefix=/usr --sysconfdir=/etc --localstatedir=/var \ - -Dsystemdsystemunitdir=/usr/lib/systemd/system \ - -Dgtk-doc=false -Dman=false - meson compile -C build-x86_64 - DESTDIR="$GITHUB_WORKSPACE/src/upower/dest-x86_64" meson install -C build-x86_64 - ) - tar -C "$GITHUB_WORKSPACE/src/upower/dest-x86_64" \ - -cf "$GITHUB_WORKSPACE/src/Souveraine/upower-tree-x86_64.tar" . - - # --- aarch64: cross build against the same sysroot souveraine uses --- - CROSS="$GITHUB_WORKSPACE/src/upower/aarch64-cross.ini" - cat > "$CROSS" <&2; exit 1; } - # Cross-install leak. upower asks pkg-config for the udev dir, and - # with a sysroot in play that answer comes back sysroot-prefixed, so - # the rules and hwdb install under $DESTDIR/home/casey/aarch64-sysroot/ - # instead of /usr/lib/udev. systemdsystemunitdir is passed explicitly - # above for exactly this reason; the udev dir was missed. Measured on - # the phone 2026-07-27: all four of 60-upower-battery.{rules,hwdb}, - # 95-upower-wup.rules and 95-upower-hid.hwdb were absent from - # /usr/lib/udev and present only under the junk path — so the fork's - # udev quirks had never once been applied on the device. - # Fold any leaked tree back before the tar, then refuse to ship a - # tree containing anything but the three prefixes upower legitimately - # installs into: usr/, etc/ (UPower.conf) and var/ (var/lib/upower). - # Leaving var/ out of that list is what failed run 1234. - DEST="$GITHUB_WORKSPACE/src/upower/dest-aarch64" - LEAK="$DEST$HOME/aarch64-sysroot" - if [ -d "$LEAK" ]; then - echo "cross install leaked into $HOME/aarch64-sysroot — relocating" - cp -a "$LEAK/." "$DEST/" - rm -rf "$LEAK" - # Removing the sysroot subtree leaves its empty parents behind - # ($DEST/home/casey), and an empty directory still ships AND still - # trips the stray check below — which is how runs 1234 and 1248 - # failed. The PKGBUILD copy of this had the rmdir; this one did not. - rmdir -p --ignore-fail-on-non-empty "$(dirname "$LEAK")" 2>/dev/null || true - fi - stray=$(find "$DEST" -mindepth 1 -maxdepth 1 \ - ! -name usr ! -name etc ! -name var -printf '%f\n') - if [ -n "$stray" ]; then - echo "upower tree would ship outside usr/, etc/ and var/: $stray" >&2 - exit 1 - fi - # And the rules must have landed where udev actually reads them. - for f in usr/lib/udev/rules.d/60-upower-battery.rules \ - usr/lib/udev/hwdb.d/60-upower-battery.hwdb; do - [ -e "$DEST/$f" ] || { echo "upower tree is missing $f" >&2; exit 1; } - done - tar -C "$GITHUB_WORKSPACE/src/upower/dest-aarch64" \ - -cf "$GITHUB_WORKSPACE/src/Souveraine/upower-tree-aarch64.tar" . - - - name: Stage the shell surface for packaging - run: | - set -euo pipefail - cd "$GITHUB_WORKSPACE/src/Souveraine" - # deploy.sh composes ~/.config/quickshell/souveraine from repo files - # (symlinks) plus ii dirs borrowed from the ii-base pin. Run it in a - # scratch HOME and tar the result dereferenced, so the package is - # self-contained real files and never ships dangling links. - SHELL_STAGE="$GITHUB_WORKSPACE/shell-stage" - SHELL_TAR="$GITHUB_WORKSPACE/shell-package-input" - rm -rf "$SHELL_STAGE" "$SHELL_TAR" - mkdir -p "$SHELL_STAGE" "$SHELL_TAR" - HOME="$SHELL_STAGE" ./surfaces/quickshell/deploy.sh - test -L "$SHELL_STAGE/.config/quickshell/souveraine/shell.qml" - tar -h --zstd -C "$SHELL_STAGE/.config/quickshell" -cf "$SHELL_TAR/souveraine-shell-config.tar.zst" souveraine - tar -tf "$SHELL_TAR/souveraine-shell-config.tar.zst" | grep -qx 'souveraine/shell.qml' - - - name: Package and sign pacman repository - run: | - set -euo pipefail - cd "$GITHUB_WORKSPACE/src/Souveraine" - PKGVER="0.1.r$(git rev-list --count HEAD).g${GITHUB_SHA:0:12}" - WORK="$GITHUB_WORKSPACE/pacman-package" - REPO="$GITHUB_WORKSPACE/pacman-repo" - ARCHIVE_KEY="3CD9E99E222C2A174986FC9AFF4949AA20C8E911" - rm -rf "$WORK" "$REPO" - mkdir -p "$WORK" "$REPO" - - for ARCH in aarch64 x86_64; do - PKG_WORK="$WORK/$ARCH" - ARCH_REPO="$REPO/$ARCH" - mkdir -p "$PKG_WORK" "$ARCH_REPO" - cp "souveraine-$ARCH" "$PKG_WORK/souveraine-binary" - cp "souveraine-secrets-$ARCH" "$PKG_WORK/souveraine-secrets-binary" - cp "souveraine-machined-$ARCH" "$PKG_WORK/souveraine-machined-binary" - cp "souveraine-admit-$ARCH" "$PKG_WORK/souveraine-admit-binary" - cp "souveraine-sessiond-$ARCH" "$PKG_WORK/souveraine-sessiond-binary" - cp "souveraine-sensord-$ARCH" "$PKG_WORK/souveraine-sensord-binary" - cp packaging/souveraine.service packaging/souveraine-secrets.service \ - packaging/org.freedesktop.secrets.service \ - packaging/souveraine-machined.service \ - packaging/souveraine-sessiond.service \ - packaging/souveraine-sensord.service LICENSE "$PKG_WORK/" - cp packaging/arch/souveraine.sysusers "$PKG_WORK/" - # Phone-only reporters and surfaces remain aarch64-only. - if [ "$ARCH" = aarch64 ]; then - cp "souveraine-usb-kvm-$ARCH" "$PKG_WORK/souveraine-usb-kvm-binary" - # The wry host. Guarded, and the PKGBUILD source line is removed - # when it is absent: it needs webkit2gtk in the aarch64 sysroot, - # and a missing avatar must not stop sessiond reaching the phone. - # makepkg validates every entry in `source`, so leaving a name - # there with no file fails the whole package. - if [ -f "souveraine-web-$ARCH" ]; then - cp "souveraine-web-$ARCH" "$PKG_WORK/souveraine-web-binary" - fi - fi - cp packaging/souveraine-verify-trail "$PKG_WORK/" - cp packaging/souveraine-button "$PKG_WORK/" - cp packaging/souveraine-stepup.pam packaging/souveraine-sessiond.pam \ - packaging/org.souveraine.stepup.policy "$PKG_WORK/" - cp packaging/arch/PKGBUILD.prebuilt "$PKG_WORK/PKGBUILD" - ( - cd "$PKG_WORK" - export SOUVERAINE_PKGVER="$PKGVER" - export SOUVERAINE_PKGARCH="$ARCH" - CARCH="$ARCH" makepkg --nodeps --noconfirm --cleanbuild - ) - PKG=$(find "$PKG_WORK" -maxdepth 1 -name 'souveraine-*.pkg.tar.zst' -print -quit) - test -n "$PKG" - bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine' - bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-sessiond' - bsdtar -tf "$PKG" | grep -qx 'usr/lib/systemd/user/souveraine-sessiond.service' - bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-sensord' - bsdtar -tf "$PKG" | grep -qx 'etc/pam.d/souveraine-sessiond' - bsdtar -tf "$PKG" | grep -qx 'usr/lib/sysusers.d/souveraine.conf' - bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-admit' - if [ "$ARCH" = aarch64 ]; then - bsdtar -tf "$PKG" | grep -qx 'usr/bin/souveraine-usb-kvm' - fi - cp "$PKG" "$ARCH_REPO/" - gpg --batch --yes --local-user "$ARCHIVE_KEY" \ - --detach-sign "$ARCH_REPO/$(basename "$PKG")" - - # UPower fork package — built from packaging/upower-souveraine - # (git submodule of the Souveraine fork of upstream upower). Built - # natively per-arch by the "Build UPower fork" step above into - # upower-tree-$ARCH.tar, then folded into the SAME per-arch pacman - # database so the phone installs upower-souveraine from the same - # edge archive it already uses for souveraine. - # Version from UPSTREAM upower + the fork commit, NOT souveraine's - # r-count. The hand-built package on the phone was 1.91.3+; - # reusing souveraine's 0.1.rN here sorts LOWER, so pacman treats - # the repo copy as a downgrade and -Syu silently skips it. - UPOWER_VER=$(sed -nE "s/^ *version *: *'([^']+)'.*/\1/p" \ - "$GITHUB_WORKSPACE/src/upower/meson.build" | head -1) - # '.r' not '+': a literal + in a release asset filename cannot be - # fetched from Gitea at all — it is stored decoded as a space, and - # both the raw + and %2B forms 404. Still sorts above the old - # hand-built 1.91.3+ because vercmp reads + as a separator too. - UPOWER_PKGVER="${UPOWER_VER}.r$(git -C "$GITHUB_WORKSPACE/src/upower" rev-parse --short HEAD)" - - UPOWER_WORK="$WORK/upower-$ARCH" - mkdir -p "$UPOWER_WORK" - cp "upower-tree-$ARCH.tar" "$UPOWER_WORK/upower-tree.tar" - cp packaging/upower-souveraine/dbus/org.freedesktop.UPower.Device.xml \ - "$UPOWER_WORK/" 2>/dev/null \ - || cp "$GITHUB_WORKSPACE/src/upower/dbus/org.freedesktop.UPower.Device.xml" \ - "$UPOWER_WORK/" - cp packaging/arch/PKGBUILD.upower.prebuilt "$UPOWER_WORK/PKGBUILD" - ( - cd "$UPOWER_WORK" - export SOUVERAINE_PKGVER="$UPOWER_PKGVER" - export SOUVERAINE_PKGARCH="$ARCH" - CARCH="$ARCH" makepkg --nodeps --noconfirm --cleanbuild - ) - UPKG=$(find "$UPOWER_WORK" -maxdepth 1 -name 'upower-souveraine-*.pkg.tar.zst' -print -quit) - test -n "$UPKG" - # libexec/, not lib/ — same path correction as the sanity check above. - bsdtar -tf "$UPKG" | grep -qx 'usr/libexec/upowerd' - cp "$UPKG" "$ARCH_REPO/" - gpg --batch --yes --local-user "$ARCHIVE_KEY" \ - --detach-sign "$ARCH_REPO/$(basename "$UPKG")" - - # Shell package — the composed quickshell config, arch=any, from - # the staged tar. Same per-arch database as the binaries above. - SHELL_WORK="$WORK/shell-$ARCH" - mkdir -p "$SHELL_WORK" - cp "$GITHUB_WORKSPACE/shell-package-input/souveraine-shell-config.tar.zst" "$SHELL_WORK/" - cp packaging/arch/PKGBUILD.shell.prebuilt "$SHELL_WORK/PKGBUILD" - cp LICENSE "$SHELL_WORK/" - ( - cd "$SHELL_WORK" - export SOUVERAINE_PKGVER="$PKGVER" - CARCH="$ARCH" makepkg --nodeps --noconfirm --cleanbuild - ) - SPKG=$(find "$SHELL_WORK" -maxdepth 1 -name 'souveraine-shell-*.pkg.tar.zst' -print -quit) - test -n "$SPKG" - bsdtar -tf "$SPKG" | grep -qx 'etc/xdg/quickshell/souveraine/shell.qml' - bsdtar -tf "$SPKG" | grep -qx 'usr/share/licenses/souveraine-shell/LICENSE' - cp "$SPKG" "$ARCH_REPO/" - gpg --batch --yes --local-user "$ARCHIVE_KEY" \ - --detach-sign "$ARCH_REPO/$(basename "$SPKG")" - - # The per-arch database is NOT built here. `edge` is a shared archive - # with more than one producer in it, so the database has to be - # read-modify-written against the live copy rather than generated - # fresh — packaging/arch/publish-edge.sh owns that, under a lock. - done - cp packaging/arch/souveraine-archive-key.asc "$REPO/" - - - name: Publish rolling edge prerelease - env: - # A PAT rather than the job's own GITHUB_TOKEN, so the identical - # invocation works from a producer repo other than this one. - EDGE_TOKEN: ${{ secrets.LOCAL_GITEA_TOKEN }} - ARCHIVE_KEY: 3CD9E99E222C2A174986FC9AFF4949AA20C8E911 - run: | - set -euo pipefail - cd "$GITHUB_WORKSPACE/src/Souveraine" - # Additive: the release and tag are never deleted, the live databases - # are merged into, and only this producer's own superseded packages - # are removed. Before 2026-07-25 this step deleted and recreated the - # release, which erased every other producer's packages on each push. - export EDGE_TARGET_SHA="$GITHUB_SHA" - export PRODUCER_VERSION="0.1.r$(git rev-list --count HEAD).g${GITHUB_SHA:0:12}" - ./packaging/arch/publish-edge.sh souveraine "$GITHUB_WORKSPACE/pacman-repo" \ - souveraine-aarch64 souveraine-x86_64 souveraine-binaries.sha256 \ - "$GITHUB_WORKSPACE/pacman-repo/souveraine-archive-key.asc" - - - name: Mirror into the Gitea Arch registry (alongside edge) - # Best-effort, never fails the publish: the registry sits alongside - # release-assets until proven as the phone's source. A 409 means - # (name, version) is already present immutably -- bump to republish. - # Device-side auth is handled by souveraine-pacman-fetch; this only - # uploads. Covers souveraine + upower-souveraine (both arches). - env: - EDGE_TOKEN: ${{ secrets.LOCAL_GITEA_TOKEN }} - run: | - set +e - REG="$GITHUB_SERVER_URL/api/packages/${GITHUB_REPOSITORY%/*}/arch/edge" - ok=0; skip=0; bad=0 - shopt -s nullglob - for pkg in "$GITHUB_WORKSPACE"/pacman-repo/*/*.pkg.tar.zst; do - code=$(curl -s -o /dev/null -w "%{http_code}" -X PUT \ - -H "Authorization: token $EDGE_TOKEN" --upload-file "$pkg" "$REG") - case "$code" in - 200|201) echo "registry: published $(basename "$pkg")"; ok=$((ok+1));; - 409) echo "registry: present $(basename "$pkg") (bump to republish)"; skip=$((skip+1));; - *) echo "::warning::registry $(basename "$pkg") -> HTTP $code"; bad=$((bad+1));; - esac - done - echo "registry mirror: $ok new, $skip present, $bad failed" - exit 0