forked from marfrit/kernel-agent
Compare commits
10 Commits
731e98e079
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 84cdb5b4ee | |||
| 3d15c5367d | |||
| 588350c4da | |||
| cc6f2378ab | |||
| dd631fd3c7 | |||
| 38fd672940 | |||
| 8b356aa11f | |||
| 443f5e992e | |||
| 2f119a3fb7 | |||
| 7a86ebb587 |
@@ -264,8 +264,8 @@ build. `ka-promote` (issue #22) replaced the manual step #1 below as of 2026-05-
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `ka-import fresnel-fourier <patches> --to board/pinebook-pro` (originally named `ka-promote` in this row) | Authored 3 patches with proper headers/scope tags, pushed to `marfrit/kernel-agent/patches/board/pinebook-pro/` via Gitea contents API as `claude-noether`. | still manual — `ka-import` unimplemented |
|
| `ka-import fresnel-fourier <patches> --to board/pinebook-pro` (originally named `ka-promote` in this row) | Authored 3 patches with proper headers/scope tags, pushed to `marfrit/kernel-agent/patches/board/pinebook-pro/` via Gitea contents API as `claude-noether`. | still manual — `ka-import` unimplemented |
|
||||||
| `ka-promote fresnel` (new — manifest → cumulative.patch + manifest.lock) | n/a (didn't exist) | **automated 2026-05-18, issue #22** |
|
| `ka-promote fresnel` (new — manifest → cumulative.patch + manifest.lock) | n/a (didn't exist) | **automated 2026-05-18, issue #22** |
|
||||||
| `ka-build fresnel` | On boltzmann: cloned linux v7.0 from kernel.org, ran `makepkg -s --skipchecksums --skippgpcheck` against `marfrit-packages/arch/linux-fresnel-fourier/PKGBUILD`. Native aarch64 (boltzmann is RK3588). One headers-pkg bug discovered (`ln -sr` on missing parent dir) and fixed mid-flight. Repackaged. | still manual — next verb to implement |
|
| `ka-build fresnel` | On boltzmann: cloned linux v7.0 from kernel.org, ran `makepkg -s --skipchecksums --skippgpcheck` against `marfrit-packages/arch/linux-fresnel-fourier/PKGBUILD`. Native aarch64 (boltzmann is RK3588). One headers-pkg bug discovered (`ln -sr` on missing parent dir) and fixed mid-flight. Repackaged. | **automated 2026-05-19, issue #34** — `ka-build <host>` ssh-dispatches makepkg to `build_host.primary`, verifies kernel-agent patches still match the PKGBUILD-side files (b2sum cross-check from `manifest.lock`), and pulls the resulting `*.pkg.tar.zst` back. |
|
||||||
| `ka-sign + push` | scp pkgs hertz → `sudo /opt/herding/bin/marfrit-publish-arch aarch64 <pkg>` per pkg. Script signs with key `92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C`, runs repo-add, rsyncs to nc. | still manual — folded into `ka-build` |
|
| `ka-sign + push` | scp pkgs hertz → `sudo /opt/herding/bin/marfrit-publish-arch aarch64 <pkg>` per pkg. Script signs with key `92D5E96D8F63C75E4116AA1FF5C8C4603D0D250C`, runs repo-add, rsyncs to nc. | **folded into `ka-build` 2026-05-19** — `ka-build` scp's each pkg to hertz and runs `marfrit-publish-arch` over ssh. `--skip-publish` flag retained for offline builds. |
|
||||||
| `ka-install fresnel` (consent-via-action) | `sudo pacman -U /tmp/<pkg>` over LAN scp (HTTPS to nc was throttled by fresnel's wifi). pacman post-transaction hook updated extlinux. mkinitcpio run manually because the standard hook trigger watches `vmlinuz` not `Image`. | still manual — last verb to implement |
|
| `ka-install fresnel` (consent-via-action) | `sudo pacman -U /tmp/<pkg>` over LAN scp (HTTPS to nc was throttled by fresnel's wifi). pacman post-transaction hook updated extlinux. mkinitcpio run manually because the standard hook trigger watches `vmlinuz` not `Image`. | still manual — last verb to implement |
|
||||||
| Bar 1..3 verification | SSH heartbeat OK, `pacman -Q linux-fresnel-fourier` = `7.0-1`, post-reboot cluster0 1.704 GHz / cluster1 2.184 GHz confirmed. | folded into `ka-install` |
|
| Bar 1..3 verification | SSH heartbeat OK, `pacman -Q linux-fresnel-fourier` = `7.0-1`, post-reboot cluster0 1.704 GHz / cluster1 2.184 GHz confirmed. | folded into `ka-install` |
|
||||||
|
|
||||||
|
|||||||
Executable
+199
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ka-build — render PKGBUILD from manifest.lock, build native on host,
|
||||||
|
# sign+publish via marfrit-publish-arch on hertz.
|
||||||
|
#
|
||||||
|
# Phase-1 (issue #34): arch makepkg wrapper. Debian path deferred.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ka-build <host>
|
||||||
|
# ka-build <host> --packages-repo <path> # default: ~/src/marfrit-packages
|
||||||
|
# ka-build <host> --dry-run # stop after staging, don't makepkg
|
||||||
|
# ka-build <host> --skip-publish # build only, don't push to hertz
|
||||||
|
#
|
||||||
|
# Exit codes:
|
||||||
|
# 0 success (pkg built + published)
|
||||||
|
# 2 missing input (manifest.lock, PKGBUILD, ssh target)
|
||||||
|
# 3 patch drift (resolved.sha256 != PKGBUILD-side file sha256)
|
||||||
|
# 4 makepkg / sign / publish failure
|
||||||
|
# 5 manifest parse error
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
VERSION=1
|
||||||
|
|
||||||
|
die() { echo "ka-build: error: $1" >&2; exit "${2:-1}"; }
|
||||||
|
note() { echo "ka-build: $1"; }
|
||||||
|
|
||||||
|
# Defaults
|
||||||
|
PACKAGES_REPO="${KA_PACKAGES_REPO:-${HOME}/src/marfrit-packages}"
|
||||||
|
DRY_RUN=0
|
||||||
|
SKIP_PUBLISH=0
|
||||||
|
HOST=""
|
||||||
|
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--packages-repo) PACKAGES_REPO="$2"; shift 2 ;;
|
||||||
|
--dry-run) DRY_RUN=1; shift ;;
|
||||||
|
--skip-publish) SKIP_PUBLISH=1; shift ;;
|
||||||
|
--version) echo "ka-build version $VERSION"; exit 0 ;;
|
||||||
|
-h|--help) sed -n '1,30p' "$0" | grep -E '^# ' | sed 's/^# //'; exit 0 ;;
|
||||||
|
-*) die "unknown flag: $1" ;;
|
||||||
|
*) [ -z "$HOST" ] && HOST="$1" || die "extra arg: $1"; shift ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "$HOST" ] || die "host is required" 2
|
||||||
|
[ -d "$PACKAGES_REPO" ] || die "--packages-repo not found: $PACKAGES_REPO" 2
|
||||||
|
|
||||||
|
# Locate kernel-agent repo root (where bin/ + fleet/ live)
|
||||||
|
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
REPO_ROOT="$(cd "$script_dir/.." && pwd)"
|
||||||
|
[ -d "$REPO_ROOT/fleet" ] || die "fleet/ not found relative to $script_dir" 2
|
||||||
|
|
||||||
|
manifest="$REPO_ROOT/fleet/${HOST}.yaml"
|
||||||
|
[ -f "$manifest" ] || die "no manifest for host '$HOST': $manifest" 2
|
||||||
|
|
||||||
|
# Read fields from manifest via python (yaml in bash is masochism)
|
||||||
|
py_read() {
|
||||||
|
python3 -c "
|
||||||
|
import sys, yaml, os
|
||||||
|
m = yaml.safe_load(open('$manifest'))
|
||||||
|
keys = '$1'.split('.')
|
||||||
|
v = m
|
||||||
|
for k in keys:
|
||||||
|
if not isinstance(v, dict) or k not in v: sys.exit('missing key: $1')
|
||||||
|
v = v[k]
|
||||||
|
print(v)
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
PKG_NAME="$(py_read package.name)"
|
||||||
|
BASELINE_REF="$(py_read baseline.ref)"
|
||||||
|
BUILD_HOST="$(py_read build_host.primary)"
|
||||||
|
|
||||||
|
# Locate the most recent ka-promote output
|
||||||
|
build_dir_root="${KA_BUILD_DIR:-$REPO_ROOT/build}"
|
||||||
|
promote_out="${build_dir_root}/${HOST}/${BASELINE_REF}"
|
||||||
|
lock="${promote_out}/manifest.lock"
|
||||||
|
cumulative="${promote_out}/cumulative.patch"
|
||||||
|
[ -f "$lock" ] || die "no manifest.lock at $lock — run 'ka-promote $HOST' first" 2
|
||||||
|
[ -f "$cumulative" ] || die "no cumulative.patch at $cumulative — run 'ka-promote $HOST' first" 2
|
||||||
|
|
||||||
|
# Locate the PKGBUILD
|
||||||
|
pkg_dir="${PACKAGES_REPO}/arch/${PKG_NAME}"
|
||||||
|
pkgbuild="${pkg_dir}/PKGBUILD"
|
||||||
|
[ -f "$pkgbuild" ] || die "no PKGBUILD at $pkgbuild (expected from manifest package.name)" 2
|
||||||
|
|
||||||
|
note "host=$HOST pkg=$PKG_NAME baseline=$BASELINE_REF build_host=$BUILD_HOST"
|
||||||
|
note "PKGBUILD: $pkgbuild"
|
||||||
|
note "manifest.lock: $lock"
|
||||||
|
|
||||||
|
# Refuse if PKGBUILD-side patches drifted from kernel-agent patches/.
|
||||||
|
# manifest.lock.resolved_patches[].sha256 must match PKGBUILD-dir-side
|
||||||
|
# files of the same basename. (If a patch is in resolved but missing from
|
||||||
|
# PKGBUILD dir, fail loud — operator needs to sync.)
|
||||||
|
note "verifying patch consistency between kernel-agent and marfrit-packages..."
|
||||||
|
drift=0
|
||||||
|
while IFS=$'\t' read -r basename expected_sha; do
|
||||||
|
pkg_side="${pkg_dir}/${basename}"
|
||||||
|
if [ ! -f "$pkg_side" ]; then
|
||||||
|
echo " MISSING in PKGBUILD dir: $basename" >&2
|
||||||
|
drift=1; continue
|
||||||
|
fi
|
||||||
|
actual_sha=$(sha256sum "$pkg_side" | cut -d' ' -f1)
|
||||||
|
if [ "$actual_sha" != "$expected_sha" ]; then
|
||||||
|
echo " DRIFT: $basename (expected $expected_sha, got $actual_sha)" >&2
|
||||||
|
drift=1
|
||||||
|
fi
|
||||||
|
done < <(python3 -c "
|
||||||
|
import yaml, sys, os
|
||||||
|
lk = yaml.safe_load(open('$lock'))
|
||||||
|
for r in lk['resolved_patches']:
|
||||||
|
bn = os.path.basename(r['include'])
|
||||||
|
print(f\"{bn}\t{r['sha256']}\")
|
||||||
|
")
|
||||||
|
[ "$drift" -eq 0 ] || die "patches differ between kernel-agent and marfrit-packages — sync first" 3
|
||||||
|
note "patches OK ($(python3 -c "import yaml; print(len(yaml.safe_load(open('$lock'))['resolved_patches']))") files)"
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" -eq 1 ]; then
|
||||||
|
note "--dry-run: stopping before makepkg"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Stage build dir on the build host via ssh
|
||||||
|
note "staging build on ${BUILD_HOST}..."
|
||||||
|
remote_stage="/tmp/ka-build-${HOST}-$$"
|
||||||
|
ssh "${BUILD_HOST}" "mkdir -p '$remote_stage'"
|
||||||
|
rsync -a "${pkg_dir}/" "${BUILD_HOST}:${remote_stage}/"
|
||||||
|
|
||||||
|
# Run makepkg natively
|
||||||
|
note "running makepkg --syncdeps --noconfirm --cleanbuild on ${BUILD_HOST}..."
|
||||||
|
ssh "${BUILD_HOST}" "cd '$remote_stage' && makepkg --syncdeps --noconfirm --cleanbuild --skipchecksums" \
|
||||||
|
|| die "makepkg failed on ${BUILD_HOST}" 4
|
||||||
|
|
||||||
|
# Fetch built packages
|
||||||
|
note "fetching .pkg.tar.zst from ${BUILD_HOST}..."
|
||||||
|
local_out="${promote_out}/pkgs"
|
||||||
|
mkdir -p "$local_out"
|
||||||
|
rsync -av "${BUILD_HOST}:${remote_stage}/*.pkg.tar.zst" "$local_out/" 2>&1 | tail -5
|
||||||
|
|
||||||
|
# Compute b2sums
|
||||||
|
pkg_b2sum_list=$(cd "$local_out" && for p in *.pkg.tar.zst; do
|
||||||
|
[ -f "$p" ] || continue
|
||||||
|
printf '%s %s\n' "$(b2sum "$p" | cut -d' ' -f1)" "$p"
|
||||||
|
done)
|
||||||
|
note "built packages:"
|
||||||
|
echo "$pkg_b2sum_list" | sed 's/^/ /'
|
||||||
|
|
||||||
|
# Publish via hertz marfrit-publish-arch (unless --skip-publish)
|
||||||
|
if [ "$SKIP_PUBLISH" -eq 0 ]; then
|
||||||
|
note "publishing to packages.reauktion.de/arch/aarch64/..."
|
||||||
|
for p in "$local_out"/*.pkg.tar.zst; do
|
||||||
|
[ -f "$p" ] || continue
|
||||||
|
base="$(basename "$p")"
|
||||||
|
scp -q "$p" "hertz:/tmp/${base}" || die "scp to hertz failed: $base" 4
|
||||||
|
ssh hertz "sudo /opt/herding/bin/marfrit-publish-arch aarch64 '/tmp/${base}'" \
|
||||||
|
|| die "marfrit-publish-arch failed: $base" 4
|
||||||
|
ssh hertz "rm -f '/tmp/${base}'"
|
||||||
|
note "published: $base"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Update manifest.lock with build receipt (append; don't rewrite the
|
||||||
|
# existing fields)
|
||||||
|
note "writing build receipt to manifest.lock..."
|
||||||
|
python3 - <<PY
|
||||||
|
import yaml, os, hashlib
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
lock_path = "$lock"
|
||||||
|
out_dir = "$local_out"
|
||||||
|
build_host = "$BUILD_HOST"
|
||||||
|
skipped = $SKIP_PUBLISH
|
||||||
|
|
||||||
|
lk = yaml.safe_load(open(lock_path))
|
||||||
|
epoch = os.environ.get("SOURCE_DATE_EPOCH")
|
||||||
|
if epoch:
|
||||||
|
built_at = datetime.fromtimestamp(int(epoch), tz=timezone.utc).isoformat()
|
||||||
|
else:
|
||||||
|
built_at = datetime.now(tz=timezone.utc).isoformat()
|
||||||
|
|
||||||
|
pkgs = []
|
||||||
|
for fn in sorted(os.listdir(out_dir)):
|
||||||
|
if not fn.endswith(".pkg.tar.zst"): continue
|
||||||
|
fp = os.path.join(out_dir, fn)
|
||||||
|
b2 = hashlib.blake2b(open(fp, "rb").read()).hexdigest()
|
||||||
|
pkgs.append({"name": fn, "size": os.path.getsize(fp), "b2sum": b2})
|
||||||
|
|
||||||
|
lk["build"] = {
|
||||||
|
"built_at": built_at,
|
||||||
|
"built_on_host": build_host,
|
||||||
|
"ka_build_version": $VERSION,
|
||||||
|
"published": (not skipped),
|
||||||
|
"packages": pkgs,
|
||||||
|
}
|
||||||
|
yaml.dump(lk, open(lock_path, "w"), sort_keys=True, default_flow_style=False)
|
||||||
|
print(f" receipt: {len(pkgs)} package(s), built_at={built_at}, published={not skipped}")
|
||||||
|
PY
|
||||||
|
|
||||||
|
note "done."
|
||||||
+39
-2
@@ -27,6 +27,7 @@ import argparse
|
|||||||
import glob
|
import glob
|
||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
@@ -37,6 +38,17 @@ VERSION = 1
|
|||||||
SCHEMA_VERSION = 1
|
SCHEMA_VERSION = 1
|
||||||
COVER_LETTER = "0000-cover-letter.patch"
|
COVER_LETTER = "0000-cover-letter.patch"
|
||||||
|
|
||||||
|
# git format-patch trailer: "-- \n<MAJOR>.<MINOR>(.<PATCH>)?\n" at EOF,
|
||||||
|
# possibly with trailing blank line(s). Strip from each source patch so
|
||||||
|
# that the cumulative is always well-formed regardless of include order.
|
||||||
|
# See issue #31.
|
||||||
|
_TRAILER_RE = re.compile(rb'\n-- \n\d+\.\d+(?:\.\d+)?\n+\Z')
|
||||||
|
|
||||||
|
# Canonical separator emitted between concatenated patches in the
|
||||||
|
# cumulative. Trailing blank line keeps patch(1) happy when the next
|
||||||
|
# patch starts with "From <sha>".
|
||||||
|
_CANONICAL_TRAILER = b'-- \n2.54.0\n\n'
|
||||||
|
|
||||||
|
|
||||||
def die(msg, code=1):
|
def die(msg, code=1):
|
||||||
print(f"ka-promote: error: {msg}", file=sys.stderr)
|
print(f"ka-promote: error: {msg}", file=sys.stderr)
|
||||||
@@ -124,11 +136,36 @@ def resolve_includes(includes, patches_root):
|
|||||||
return resolved
|
return resolved
|
||||||
|
|
||||||
|
|
||||||
|
def strip_trailer(data):
|
||||||
|
"""Strip any trailing git format-patch sentinel from a patch.
|
||||||
|
|
||||||
|
Accepts patches in either canonical shape:
|
||||||
|
- WITH trailer: "...\n-- \n2.54.0\n\n"
|
||||||
|
- WITHOUT trailer: "...\n" (already stripped)
|
||||||
|
|
||||||
|
Returns data ending in a single newline so the caller can either
|
||||||
|
append a canonical trailer (mid-cumulative) or leave it bare (last).
|
||||||
|
"""
|
||||||
|
stripped = _TRAILER_RE.sub(b'\n', data)
|
||||||
|
if not stripped.endswith(b'\n'):
|
||||||
|
stripped += b'\n'
|
||||||
|
return stripped
|
||||||
|
|
||||||
|
|
||||||
def write_cumulative(resolved, out_path):
|
def write_cumulative(resolved, out_path):
|
||||||
with open(out_path, "wb") as out:
|
with open(out_path, "wb") as out:
|
||||||
for r in resolved:
|
n = len(resolved)
|
||||||
|
for i, r in enumerate(resolved):
|
||||||
with open(r["src"], "rb") as src:
|
with open(r["src"], "rb") as src:
|
||||||
out.write(src.read())
|
data = src.read()
|
||||||
|
data = strip_trailer(data)
|
||||||
|
out.write(data)
|
||||||
|
# Mid-cumulative patches need a separator so patch(1) knows
|
||||||
|
# where they end and the next "From <sha>" begins. Last
|
||||||
|
# patch stays bare — a trailing orphan sentinel reads as
|
||||||
|
# the start of a malformed new patch at EOF (issue #31).
|
||||||
|
if i != n - 1:
|
||||||
|
out.write(_CANONICAL_TRAILER)
|
||||||
with open(out_path, "rb") as f:
|
with open(out_path, "rb") as f:
|
||||||
b2 = hashlib.blake2b(f.read()).hexdigest()
|
b2 = hashlib.blake2b(f.read()).hexdigest()
|
||||||
size = os.path.getsize(out_path)
|
size = os.path.getsize(out_path)
|
||||||
|
|||||||
+17
-1
@@ -1,6 +1,6 @@
|
|||||||
# kernel-agent manifest for ohm (PineTab2 / Rockchip RK3566 + BES2600 SDIO WiFi/BT)
|
# kernel-agent manifest for ohm (PineTab2 / Rockchip RK3566 + BES2600 SDIO WiFi/BT)
|
||||||
#
|
#
|
||||||
# Status: scaffolding from 2026-05-16. Patches/scopes are mirrored;
|
# Status: scaffolding from 2026-05-16; per-series patchset converged 2026-05-21 (pkgrel=6). Patches/scopes are mirrored;
|
||||||
# the build pipeline (cumulative-patch generation, makepkg invocation,
|
# the build pipeline (cumulative-patch generation, makepkg invocation,
|
||||||
# sign+publish) still relies on the hand-managed flow in
|
# sign+publish) still relies on the hand-managed flow in
|
||||||
# boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/.
|
# boltzmann:~/src/besser/marfrit-besser/danctnix-besser-pkgbuild/kernel/.
|
||||||
@@ -32,6 +32,13 @@ baseline:
|
|||||||
# mixed-prefix headers (a/drivers/staging/bes2600/... b/bes2600/...).
|
# mixed-prefix headers (a/drivers/staging/bes2600/... b/bes2600/...).
|
||||||
# They do NOT apply cleanly against the linux-pinetab2 baseline.
|
# They do NOT apply cleanly against the linux-pinetab2 baseline.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# 2026-05-21 update: per-series reconstruction (besser#22) completed
|
||||||
|
# 2026-05-21; pkgrel=6 (srcversion 0E16463F) on ohm soak-passed with
|
||||||
|
# the bounce-buffer + join-confirm-reset additions. The per-series
|
||||||
|
# manifest below is the authoritative set; cumulative-c5x-danctnix
|
||||||
|
# remains as historical fallback only.
|
||||||
|
#
|
||||||
# Until the per-series mirrors are reconstructed (kernel-agent followup
|
# Until the per-series mirrors are reconstructed (kernel-agent followup
|
||||||
# issue), the bes2600 driver scope is satisfied by a single-file
|
# issue), the bes2600 driver scope is satisfied by a single-file
|
||||||
# cumulative captured from the working hand-managed
|
# cumulative captured from the working hand-managed
|
||||||
@@ -52,6 +59,15 @@ includes:
|
|||||||
# close besser#18 — pending_record_lock SOFTIRQ-safe -> -unsafe inversion.
|
# close besser#18 — pending_record_lock SOFTIRQ-safe -> -unsafe inversion.
|
||||||
# Mirror of marfrit/bes2600-dkms#11 (d95453c). 5-site spin_lock -> _bh.
|
# Mirror of marfrit/bes2600-dkms#11 (d95453c). 5-site spin_lock -> _bh.
|
||||||
- driver/bes2600/queue-pending-record-lock-bh-danctnix/
|
- driver/bes2600/queue-pending-record-lock-bh-danctnix/
|
||||||
|
# bounce-buffer fix for SDIO TX DMA OOB (KFENCE-detected on pkgrel=4 soak);
|
||||||
|
# the per-series mirror of marfrit/bes2600-dkms bes2600/tx-sdio-dma-oob.
|
||||||
|
# cumulative-c5x-danctnix did NOT include this — it was the regression
|
||||||
|
# surfaced during the per-series reconstruction.
|
||||||
|
- driver/bes2600/tx-sdio-dma-oob-danctnix/
|
||||||
|
# close besser#25 — wsm_reset + serialised unjoin on JOIN reject.
|
||||||
|
# cw1200 ancestor port (drivers/net/wireless/st/cw1200/sta.c:1339-1344)
|
||||||
|
# with bes2600-specific PASSIVE-gate compensation. pkgrel=6 verified.
|
||||||
|
- driver/bes2600/join-confirm-reset-danctnix/
|
||||||
|
|
||||||
# Explicitly NOT included (decision logged):
|
# Explicitly NOT included (decision logged):
|
||||||
# - debian-copyright-fsf-address: Debian packaging metadata, not kernel
|
# - debian-copyright-fsf-address: Debian packaging metadata, not kernel
|
||||||
|
|||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
From 3d833f8ccf31895a2ce7bf4fd4ef839e653b29bb Mon Sep 17 00:00:00 2001
|
||||||
|
From: Markus Fritsche <fritsche.markus@gmail.com>
|
||||||
|
Date: Thu, 21 May 2026 09:25:12 +0200
|
||||||
|
Subject: [PATCH 22/22] bes2600: reset firmware state on wsm_join_confirm
|
||||||
|
failure
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/plain; charset=UTF-8
|
||||||
|
Content-Transfer-Encoding: 8bit
|
||||||
|
|
||||||
|
When wsm_join_confirm() returns status != WSM_STATUS_SUCCESS (ret 1),
|
||||||
|
the driver cleared its bookkeeping but did not reset the firmware
|
||||||
|
interface, leaving it in an intermediate post-rejection state. A rapid
|
||||||
|
second JOIN attempt (e.g. wpa_supplicant retrying after the
|
||||||
|
PREV_AUTH_NOT_VALID deauth that mac80211 emits to clean up) hits an
|
||||||
|
inconsistent firmware context, causing bes2600_sdio_read_rx_batch to
|
||||||
|
return SDIO error which cascades into wifi_force_close:
|
||||||
|
|
||||||
|
wsm_join_confirm ret 1
|
||||||
|
deauthenticating from <bssid> by local choice (Reason: 2=PREV_AUTH_NOT_VALID)
|
||||||
|
[~10 min later]
|
||||||
|
bes2600_sdio_read_rx_batch sdio read error
|
||||||
|
WARNING: at bes2600_tx_loop_set_enable / bes2600_chrdev_wifi_force_close
|
||||||
|
|
||||||
|
Two additions to the failure path in bes2600_join_work():
|
||||||
|
|
||||||
|
1. wsm_reset (WSM_REQ_ID_RESET, 0x000A) with reset_statistics=false.
|
||||||
|
This returns the firmware to IDLE so the next association attempt
|
||||||
|
starts from a known-clean state. bes2600_unjoin_work() performs the
|
||||||
|
same reset, but gates it on join_status != PASSIVE; after a failed
|
||||||
|
JOIN join_status stays PASSIVE, so that path never fires — call
|
||||||
|
wsm_reset directly here instead.
|
||||||
|
|
||||||
|
Contract: wsm_reset takes only wsm_cmd_lock (not conf_lock, not
|
||||||
|
wsm_oper_lock). wsm_oper_unlock was already called inside
|
||||||
|
wsm_join_confirm() before wsm_join() returned -EINVAL, so there is
|
||||||
|
no re-entrancy hazard. conf_lock is held at this call site, which is
|
||||||
|
compatible with wsm_reset's locking requirements.
|
||||||
|
|
||||||
|
2. queue_work(workqueue, &priv->unjoin_work) instead of direct
|
||||||
|
wsm_unlock_tx(). Serialises the next association attempt through
|
||||||
|
the workqueue so it cannot race against lingering firmware-side
|
||||||
|
effects of the failure. If unjoin_work is already queued, release
|
||||||
|
TX immediately (matching cw1200 ancestor sta.c:1344 comment "Tx lock
|
||||||
|
still held, unjoin will clear it.").
|
||||||
|
|
||||||
|
Ancestor reference: drivers/net/wireless/st/cw1200/sta.c, function
|
||||||
|
cw1200_join_work(), lines 1339-1344. cw1200 queues unjoin_work on join
|
||||||
|
failure for the same reason. bes2600 needs the direct wsm_reset in
|
||||||
|
addition because its unjoin_work has the join_status gate that cw1200's
|
||||||
|
cw1200_do_unjoin() does not.
|
||||||
|
|
||||||
|
Signed-off-by: Claude (noether) <claude@reauktion.de>
|
||||||
|
---
|
||||||
|
bes2600/sta.c | 47 +++++++++++++++++++++++++++++++++++++++++++----
|
||||||
|
1 file changed, 43 insertions(+), 4 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/drivers/staging/bes2600/sta.c b/drivers/staging/bes2600/sta.c
|
||||||
|
index 476d875..bf86835 100644
|
||||||
|
--- a/drivers/staging/bes2600/sta.c
|
||||||
|
+++ b/drivers/staging/bes2600/sta.c
|
||||||
|
@@ -2225,9 +2225,10 @@ void bes2600_join_work(struct work_struct *work)
|
||||||
|
struct wsm_template_frame probe_tmp = {
|
||||||
|
.frame_type = WSM_FRAME_TYPE_PROBE_REQUEST,
|
||||||
|
};
|
||||||
|
- /*struct wsm_reset reset = {
|
||||||
|
- .reset_statistics = true,
|
||||||
|
- };*/
|
||||||
|
+ struct wsm_reset join_fail_reset = {
|
||||||
|
+ .reset_statistics = false,
|
||||||
|
+ };
|
||||||
|
+ bool join_failed = false;
|
||||||
|
|
||||||
|
|
||||||
|
BUG_ON(queueId >= 4);
|
||||||
|
@@ -2410,6 +2411,33 @@ void bes2600_join_work(struct work_struct *work)
|
||||||
|
#endif /*CONFIG_BES2600_TESTMODE*/
|
||||||
|
cancel_delayed_work_sync(&priv->join_timeout);
|
||||||
|
bes2600_pwr_clear_busy_event(priv->hw_priv, BES_PWR_LOCK_ON_JOIN);
|
||||||
|
+ /*
|
||||||
|
+ * Firmware rejected WSM_JOIN (wsm_join_confirm ret 1).
|
||||||
|
+ * Issue wsm_reset so the firmware returns to a clean
|
||||||
|
+ * IDLE state before the next association attempt.
|
||||||
|
+ *
|
||||||
|
+ * Without this reset the firmware sits in an
|
||||||
|
+ * intermediate post-reject state. A rapid second
|
||||||
|
+ * JOIN (e.g. wpa_supplicant retrying after the
|
||||||
|
+ * PREV_AUTH_NOT_VALID deauth that follows) hits an
|
||||||
|
+ * inconsistent firmware context, causing
|
||||||
|
+ * bes2600_sdio_read_rx_batch to return SDIO error
|
||||||
|
+ * which cascades into wifi_force_close.
|
||||||
|
+ *
|
||||||
|
+ * cw1200 ancestor (drivers/net/wireless/st/cw1200/
|
||||||
|
+ * sta.c:1339) queues unjoin_work on join failure for
|
||||||
|
+ * the same reason; bes2600_unjoin_work gates its
|
||||||
|
+ * wsm_reset on join_status != PASSIVE, so after a
|
||||||
|
+ * failed JOIN (join_status stays PASSIVE) that path
|
||||||
|
+ * never fires — call wsm_reset directly here instead.
|
||||||
|
+ *
|
||||||
|
+ * Contract: wsm_reset takes only wsm_cmd_lock; safe
|
||||||
|
+ * to call while conf_lock is held. wsm_oper_unlock
|
||||||
|
+ * was already called in wsm_join_confirm() before
|
||||||
|
+ * wsm_join() returned the error.
|
||||||
|
+ */
|
||||||
|
+ WARN_ON(wsm_reset(hw_priv, &join_fail_reset, priv->if_id));
|
||||||
|
+ join_failed = true;
|
||||||
|
} else {
|
||||||
|
/* Upload keys */
|
||||||
|
#ifdef CONFIG_BES2600_TESTMODE
|
||||||
|
@@ -2434,7 +2462,18 @@ void bes2600_join_work(struct work_struct *work)
|
||||||
|
up(&hw_priv->conf_lock);
|
||||||
|
if (bss)
|
||||||
|
cfg80211_put_bss(hw_priv->hw->wiphy, bss);
|
||||||
|
- wsm_unlock_tx(hw_priv);
|
||||||
|
+ /*
|
||||||
|
+ * On join failure: queue unjoin_work so the next association
|
||||||
|
+ * attempt is serialised after any lingering cleanup, matching
|
||||||
|
+ * cw1200 sta.c:1344 "Tx lock still held, unjoin will clear it."
|
||||||
|
+ * If unjoin_work is already queued, release TX immediately.
|
||||||
|
+ */
|
||||||
|
+ if (join_failed) {
|
||||||
|
+ if (queue_work(hw_priv->workqueue, &priv->unjoin_work) <= 0)
|
||||||
|
+ wsm_unlock_tx(hw_priv);
|
||||||
|
+ } else {
|
||||||
|
+ wsm_unlock_tx(hw_priv);
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
||||||
|
void bes2600_join_timeout(struct work_struct *work)
|
||||||
|
--
|
||||||
|
2.54.0
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# bes2600/join-confirm-reset-danctnix
|
||||||
|
|
||||||
|
Danctnix-flavor patch closing besser#25 (wsm_join_confirm failure cascade).
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
When firmware returns status 1 on a JOIN command (`wsm_join_confirm ret 1`),
|
||||||
|
add a direct `wsm_reset(...)` call so the firmware returns to a clean IDLE
|
||||||
|
state, plus `queue_work(workqueue, &priv->unjoin_work)` for serialisation of
|
||||||
|
the next association attempt.
|
||||||
|
|
||||||
|
## Why it's a fork-divergence fix
|
||||||
|
|
||||||
|
`cw1200_join_work()` (cw1200 ancestor, `drivers/net/wireless/st/cw1200/sta.c:1339-1344`)
|
||||||
|
queues `unjoin_work` on join failure: `cw1200_do_unjoin()` calls `wsm_reset`
|
||||||
|
when `join_status == STA`.
|
||||||
|
|
||||||
|
bes2600's `bes2600_unjoin_work()` gates the same `wsm_reset` on
|
||||||
|
`join_status != PASSIVE`. After a failed JOIN, `join_status` stays PASSIVE
|
||||||
|
(only set to STA on success) — queuing `unjoin_work` alone is insufficient
|
||||||
|
on bes2600. The danctnix variant carries a direct `wsm_reset` in the
|
||||||
|
failure path *and* the queue_work serialisation.
|
||||||
|
|
||||||
|
## Observable effects (pkgrel=6 soak)
|
||||||
|
|
||||||
|
Beyond closing the cascade (besser#25 acceptance), this patch also
|
||||||
|
collapsed the periodic ~600 ms latency jitter on ohm:
|
||||||
|
|
||||||
|
| | pkgrel=5 | pkgrel=6 |
|
||||||
|
|---|---|---|
|
||||||
|
| max RTT | 612 ms | 13.9 ms |
|
||||||
|
| mdev | 103.5 ms | 1.55 ms |
|
||||||
|
|
||||||
|
The bgscan-driven roam-attempt to a 5 GHz BSSID followed by `wsm_join`
|
||||||
|
reject was briefly stalling TX every minute even when the cascade did
|
||||||
|
not fire.
|
||||||
|
|
||||||
|
## Upstream
|
||||||
|
|
||||||
|
- besser issue: marfrit/besser#25
|
||||||
|
- bes2600-dkms branch (Mobian flavor): bes2600/wsm-join-confirm-reset
|
||||||
|
(PR #12 against `cleanups`)
|
||||||
|
- bes2600-dkms branch (danctnix flavor): bes2600/join-confirm-failure-reset
|
||||||
|
(top commit `3d833f8`)
|
||||||
|
- shipped as patch 0022 in danctnix-besser-pkgbuild kernel/ (pkgrel=6,
|
||||||
|
srcversion 0E16463FA8D85F4704DE93F)
|
||||||
Executable
+113
@@ -0,0 +1,113 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ka-build test suite — dry-run paths only.
|
||||||
|
#
|
||||||
|
# Phase-1 deliverable per issue #34. The full makepkg path is exercised
|
||||||
|
# manually on boltzmann (parity test against the most recent hand-built
|
||||||
|
# linux-fresnel-fourier pkg); not in this suite because:
|
||||||
|
# - Needs real ssh to boltzmann + ~30 min build wall time
|
||||||
|
# - Hermetic sandbox would need a mock marfrit-publish-arch on hertz
|
||||||
|
# Future-work: add a `--mock-build-host` flag + fixture builder so this
|
||||||
|
# can run in CI.
|
||||||
|
#
|
||||||
|
# What this suite covers:
|
||||||
|
# - Argument parsing + required-host check
|
||||||
|
# - manifest.yaml read + package.name / build_host.primary extraction
|
||||||
|
# - Refuses if manifest.lock missing (ka-promote not run)
|
||||||
|
# - Refuses if PKGBUILD missing
|
||||||
|
# - Refuses on patch drift between kernel-agent and marfrit-packages
|
||||||
|
# - Happy-path dry-run on fresnel (all 6 patches match)
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
packages_repo="${PACKAGES_REPO_FOR_TESTS:-${HOME}/src/marfrit-packages}"
|
||||||
|
|
||||||
|
pass=0
|
||||||
|
fail=0
|
||||||
|
results=()
|
||||||
|
note() { printf ' %s\n' "$*"; }
|
||||||
|
ok() { results+=("PASS $1"); pass=$((pass+1)); note "PASS"; }
|
||||||
|
ko() { results+=("FAIL $1: $2"); fail=$((fail+1)); note "FAIL: $2"; }
|
||||||
|
|
||||||
|
# Reset build/ before running so we exercise the "no manifest.lock yet" path
|
||||||
|
rm -rf "$repo_root/build/fresnel"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Running ka-build test suite from $repo_root"
|
||||||
|
echo
|
||||||
|
|
||||||
|
# ----- 1. requires host arg -----
|
||||||
|
echo "::: requires host arg"
|
||||||
|
set +e
|
||||||
|
out=$("$repo_root/bin/ka-build" 2>&1)
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
if [ "$rc" -eq 2 ] && echo "$out" | grep -q "host is required"; then ok "requires host arg"; else ko "requires host arg" "exit=$rc out=$out"; fi
|
||||||
|
|
||||||
|
# ----- 2. unknown flag -----
|
||||||
|
echo "::: unknown flag rejected"
|
||||||
|
set +e
|
||||||
|
out=$("$repo_root/bin/ka-build" fresnel --nonsense 2>&1)
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
if [ "$rc" -ne 0 ] && echo "$out" | grep -q "unknown flag"; then ok "unknown flag rejected"; else ko "unknown flag rejected" "exit=$rc out=$out"; fi
|
||||||
|
|
||||||
|
# ----- 3. refuses if manifest.lock missing -----
|
||||||
|
echo "::: refuses if manifest.lock missing (ka-promote not run)"
|
||||||
|
set +e
|
||||||
|
out=$("$repo_root/bin/ka-build" fresnel --dry-run --packages-repo "$packages_repo" 2>&1)
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
if [ "$rc" -eq 2 ] && echo "$out" | grep -q "no manifest.lock"; then ok "refuses no-lock"; else ko "refuses no-lock" "exit=$rc out=$out"; fi
|
||||||
|
|
||||||
|
# Now run ka-promote so the rest can proceed
|
||||||
|
"$repo_root/bin/ka-promote" fresnel >/dev/null
|
||||||
|
|
||||||
|
# ----- 4. refuses if PKGBUILD missing -----
|
||||||
|
echo "::: refuses if PKGBUILD missing (--packages-repo wrong)"
|
||||||
|
set +e
|
||||||
|
out=$("$repo_root/bin/ka-build" fresnel --dry-run --packages-repo /tmp/non-existent-mp 2>&1)
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
if [ "$rc" -eq 2 ]; then ok "refuses bad packages-repo"; else ko "refuses bad packages-repo" "exit=$rc out=$out"; fi
|
||||||
|
|
||||||
|
# ----- 5. happy-path dry-run -----
|
||||||
|
echo "::: happy-path dry-run (fresnel, real packages-repo)"
|
||||||
|
if [ ! -f "$packages_repo/arch/linux-fresnel-fourier/PKGBUILD" ]; then
|
||||||
|
note "SKIP: $packages_repo/arch/linux-fresnel-fourier/PKGBUILD not present"
|
||||||
|
results+=("SKIP happy-path dry-run (PKGBUILD missing locally)")
|
||||||
|
else
|
||||||
|
set +e
|
||||||
|
out=$("$repo_root/bin/ka-build" fresnel --dry-run --packages-repo "$packages_repo" 2>&1)
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
if [ "$rc" -eq 0 ] && echo "$out" | grep -q "patches OK (6 files)"; then ok "happy-path dry-run"; else ko "happy-path dry-run" "exit=$rc out=$out"; fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ----- 6. patch drift detection -----
|
||||||
|
echo "::: patch drift detection (mutate a copied patch, expect exit 3)"
|
||||||
|
if [ ! -d "$packages_repo/arch/linux-fresnel-fourier" ]; then
|
||||||
|
note "SKIP: $packages_repo/arch/linux-fresnel-fourier not present"
|
||||||
|
results+=("SKIP patch drift detection")
|
||||||
|
else
|
||||||
|
sandbox=$(mktemp -d -t ka-build-drift.XXXXXX)
|
||||||
|
cp -r "$packages_repo/arch/linux-fresnel-fourier" "$sandbox/linux-fresnel-fourier"
|
||||||
|
mkdir -p "$sandbox/arch"
|
||||||
|
mv "$sandbox/linux-fresnel-fourier" "$sandbox/arch/linux-fresnel-fourier"
|
||||||
|
# Mutate one patch so its sha256 differs from manifest.lock's recorded sha
|
||||||
|
echo "drift" >> "$sandbox/arch/linux-fresnel-fourier/0001-arm64-dts-rk3399-pinebook-pro-add-OC-OPP-tables-1704-2184.patch"
|
||||||
|
set +e
|
||||||
|
out=$("$repo_root/bin/ka-build" fresnel --dry-run --packages-repo "$sandbox" 2>&1)
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
rm -rf "$sandbox"
|
||||||
|
if [ "$rc" -eq 3 ] && echo "$out" | grep -q "DRIFT:"; then ok "patch drift detection"; else ko "patch drift detection" "exit=$rc out=$out"; fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "===================="
|
||||||
|
printf '%s\n' "${results[@]}"
|
||||||
|
echo "===================="
|
||||||
|
echo "passed: $pass"
|
||||||
|
echo "failed: $fail"
|
||||||
|
[ "$fail" -eq 0 ] || exit 1
|
||||||
@@ -12,8 +12,10 @@ set -euo pipefail
|
|||||||
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
fixtures="${repo_root}/tests/ka-promote/fixtures"
|
fixtures="${repo_root}/tests/ka-promote/fixtures"
|
||||||
|
|
||||||
# Phase-3 ground truth — recorded 2026-05-18, fresnel cumulative b2sum.
|
# Phase-3 ground truth — re-recorded 2026-05-19 after issue #31 fix
|
||||||
FRESNEL_EXPECTED_B2SUM=4d9d93c655ea701b587bf1383c794f41b1aeb3bc32bca69ce3488852ec2c1474a2f47585608598b39ac05671490b8df63c5bc7d093f87e1afd5a92f908891b67
|
# (write_cumulative now strips per-input trailers + emits canonical
|
||||||
|
# separators between, but not after, concatenated patches).
|
||||||
|
FRESNEL_EXPECTED_B2SUM=9c21751cc48ab57cdf48058cc4309752de169c567bbb898c342ff3e4a5cc79add53e3fd4217c2ae2ae7c16b0f19518cf1791907367e1ea9ef16458e1e90c05e0
|
||||||
|
|
||||||
pass=0
|
pass=0
|
||||||
fail=0
|
fail=0
|
||||||
@@ -110,6 +112,47 @@ echo
|
|||||||
echo "Running ka-promote test suite from $repo_root"
|
echo "Running ka-promote test suite from $repo_root"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
|
# ----- unit: strip_trailer + write_cumulative shape (issue #31) -----
|
||||||
|
echo "::: strip_trailer + cumulative shape (issue #31)"
|
||||||
|
python3 - "$repo_root" <<'PY'
|
||||||
|
import importlib.util, pathlib, sys, tempfile, os
|
||||||
|
root = pathlib.Path(sys.argv[1])
|
||||||
|
from importlib.machinery import SourceFileLoader
|
||||||
|
mod = SourceFileLoader("ka_promote", str(root/"bin"/"ka-promote")).load_module()
|
||||||
|
|
||||||
|
# strip_trailer accepts both shapes and yields newline-terminated body
|
||||||
|
assert mod.strip_trailer(b"...body...\n-- \n2.54.0\n\n") == b"...body...\n"
|
||||||
|
assert mod.strip_trailer(b"...body...\n-- \n2.53.0\n\n") == b"...body...\n"
|
||||||
|
assert mod.strip_trailer(b"...body...\n-- \n2.20\n\n") == b"...body...\n"
|
||||||
|
assert mod.strip_trailer(b"...body...\n") == b"...body...\n"
|
||||||
|
assert mod.strip_trailer(b"...body...") == b"...body...\n"
|
||||||
|
# Multiple trailing blanks after the version still strip
|
||||||
|
assert mod.strip_trailer(b"x\n-- \n2.54.0\n\n\n") == b"x\n"
|
||||||
|
|
||||||
|
# write_cumulative: 3 inputs (mix of with-/without-trailer), check ordering
|
||||||
|
with tempfile.TemporaryDirectory() as d:
|
||||||
|
p1 = os.path.join(d, "a.patch"); open(p1,"wb").write(b"PA\n-- \n2.54.0\n\n")
|
||||||
|
p2 = os.path.join(d, "b.patch"); open(p2,"wb").write(b"PB\n") # already bare
|
||||||
|
p3 = os.path.join(d, "c.patch"); open(p3,"wb").write(b"PC\n-- \n2.40.1\n\n")
|
||||||
|
out = os.path.join(d, "out.patch")
|
||||||
|
resolved = [{"src": p1}, {"src": p2}, {"src": p3}]
|
||||||
|
mod.write_cumulative(resolved, out)
|
||||||
|
body = open(out,"rb").read()
|
||||||
|
assert body == b"PA\n-- \n2.54.0\n\nPB\n-- \n2.54.0\n\nPC\n", repr(body)
|
||||||
|
# Last patch (PC) must NOT carry an orphan trailer at EOF
|
||||||
|
assert not body.rstrip(b"\n").endswith(b"2.40.1"), \
|
||||||
|
f"last patch's trailer leaked into cumulative: {body[-40:]!r}"
|
||||||
|
print("PASS")
|
||||||
|
PY
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
results+=("PASS strip_trailer + cumulative shape (issue #31)")
|
||||||
|
pass=$((pass+1))
|
||||||
|
else
|
||||||
|
results+=("FAIL strip_trailer + cumulative shape (issue #31)")
|
||||||
|
fail=$((fail+1))
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
|
||||||
# Use the real fleet/fresnel.yaml — copy into a sandbox so the test is hermetic.
|
# Use the real fleet/fresnel.yaml — copy into a sandbox so the test is hermetic.
|
||||||
mkdir -p /tmp/ka-promote-parity-fixture
|
mkdir -p /tmp/ka-promote-parity-fixture
|
||||||
cp "$repo_root/fleet/fresnel.yaml" /tmp/ka-promote-parity-fixture/fresnel.yaml
|
cp "$repo_root/fleet/fresnel.yaml" /tmp/ka-promote-parity-fixture/fresnel.yaml
|
||||||
|
|||||||
Reference in New Issue
Block a user