The agent's own append-only working memory. Excluded from the naming and prose
audits via .host-lintignore; never rewrite an old entry (correct in place with a
dated pointer). A fresh session reads plan/, call/, and this file to continue.
2026-07-02 — host adopted
- Adopted
host-template@565410afor UDPspeeder-simd (case a, Shallow). Stamp written byhost-lifecycle adopt; roomscast/ plan/ call/scaffolded. - Push auth: the stored git credential is the
slartibardfasttoken, which had no push rights toconnollydavid/agentic-UDPspeeder-simd(HTTP 403 on first dry-run push). The operator authorized the repo, after whichgit push --dry-runreturned "Everything up-to-date". If a later push 403s again, surface it to the operator rather than retrying blindly. - Tooling built from source with the system toolchain (rustc 1.96.1, Arch Linux). The methodology pins Rust 1.95.0 as the reproducible-build anchor; the local builds use 1.96.1, which is newer and builds clean. Record this wherever a canonical artifact hash is claimed.
- UDPspeeder-simd embeds as a bare store plus worktree at
software/udpspeeder-simd/branch_libev/(its default branch isbranch_libev, notmain). Recorded in.host-softwarewith nodeploy/artifact, so it is a source pin with no reproducible-build claim yet (migrated software). - host-lint is also a
.host-softwarecomponent (the gating tool):--install-hooksreads a component'shooksscript and builtartifactfrom its materialized worktree, so host-lint must be materialized and built for the commit gate to land.
2026-07-03 — gate green; runtimes installed; push auth is flaky
- The verify gate (
host-lifecycle software --check .) is green: both components at pin, all phase receipts recorded, prose clean, reconcile clean. The commit-msg hook blocks an ordinal tell in a message (tested:phase 1is rejected, exit 1). - Tooling on this machine, all under
~/.local:bin/host-lint,bin/host-lifecycle(v0.35.1),bin/host-prove(v0.3.0),bin/allium(3.5.0),jdk-21/(Temurin 21.0.11),share/tla2tools.jar(v1.8.0, sha256 237332bd). The gate needshost-proveon PATH because host-lint's ownkani:obligations surface once it is materialized as a component; the cheap gate probeshost-prove --help, it does not run Kani. - Two clearance details: the toolchain HAZARD (artifact with no toolchain) is waived by
repro-exempt = call/0002on host-lint (consumed tool, not reproduced here); andremapis askipreceipt, notdone, becauseremap --checkerrors on an empty or absent.host-remap, so a no-rename case-(a) adoption cannot pass adonerecheck. - Push auth to
connollydavid/agentic-UDPspeeder-simdis intermittent: the stored credential is theslartibardfasttoken, which 403s after the first few pushes landed. Ifgit push403s, surface it to the operator rather than retrying or swapping credentials. As of this entry,f8d36edand5ec59afare local only.
2026-07-03 — followed the host procedure: conformant, no upgrade available
- Ran the case-(c) upgrade/verify cycle from the
hostprocedure (github.com/connollydavid/host).host-templateupstream is still at565410a, the exact revision the.hoststamp adopted: agit fetchin the submodule found no commits after it, and565410ais an ancestor oforigin/main.host-lifecycle upgrade .reports up to date (baseline46a1fd2, 0 out of order). With no newer methodology to pull in, following the host reduced to the verify gate. - Verify gate is green.
host-lifecycle software --check .exits 0 (both components at pin, every phase receipt valid, reconcile clean, prose clean, no worktree-symlink hazards);validate plan/andvalidate call/bothok;host-lint --alland--logclean. Independently confirmed: udpspeeder-simd worktree is at pin3374e3bonbranch_libevwith a clean tree, host-lint at78804cd, and the commit-msg hook still rejects an ordinal tell (aphase 1message exits 1, a clean message exits 0). - udpspeeder-simd still carries no
.allium/.tlaspec, so the requirements and timing lanes stay inert and there is no spec-without-lane defect. Every spec found on disk belongs to a tool's own worktree, not to the software under development. - Standalone
host-lint --proseon the authored docs prints advisorynote:lines but exits 0. Those notes are below the enforced bar; the in-process prose audit insidesoftware --checkis the gate that binds and it reports no flagging or warning tropes. Most notes sit in verbatim copied-in content (theUPGRADING.mdledger,cast/applying-personas.md) that is not reworded locally. The notes are not a red gate. - Corrects the 2026-07-03 "push auth is flaky" entry above: the commits it listed as
local-only (
f8d36ed,5ec59af) are now pushed.HEAD == origin/mainand the tree is clean. Push auth may still 403 intermittently; surface it to the operator if it does.
2026-07-03 — embedded the packages feed as a second software lane
- Added
packages(a fork ofopenwrt/packages, GPL-2.0, an OpenWrt feed of Makefiles/shell/C) as a Where-room component in.host-software: source pin1d40ad9on canonical branchmaster, no build/artifact (migrated build-recipe feed, not a built artifact). Materialized tosoftware/packages/master/;software --checkis green. - Mechanics for a new component: host-lifecycle has no
software --add, so a new lane is a hand-edited.host-softwarestanza plussoftware --materialize --item <name> .(the--itemscope avoids re-touching existing worktrees). The embed and release phase receipts are written withhost-lifecycle receipt --record <phase> --component packages --disposition done|skip (--evidence|--reason) .; a new component HAZARDs on missing embed+release receipts until both are recorded. - Key rule (call/0003): commits in the packages worktree carry NO
Co-Authored-By: Claudetrailer, so the feed stays upstream-clean foropenwrt/packages. The operator confirmed the scope is packages only, so host-repo commits keep the trailer. Recorded in call/0003 and the CLAUDE.md packages project-specifics.
2026-07-03 — push-auth root cause: slartibardfast is pull-only; helper is store
- The recurring push 403 has a concrete root cause rather than flakiness.
gh api repos/connollydavid/agentic-UDPspeeder-simdshows theslartibardfastaccount haspush: false(pull only); the owner accountconnollydavid(also logged in to gh) has push. And git's credential helper here isstore(~/.git-credentials), not gh, sogh auth switch -u <user>does not change the tokengit pushsends. The store already holds slartibardfast's token, so pushes go out as slartibardfast and are denied. - To push as slartibardfast (the operator's intended identity), grant slartibardfast
write on the repo; then a plain
git pushworks with no helper change. To makegh auth switchactually route pushes, rungh auth setup-gitso git uses the active gh account's token. Do not push as connollydavid without operator direction: it changes the public push identity, and the operator is provenance-sensitive. - Pending operator decision (asked, away from keyboard): grant slartibardfast write,
push as connollydavid, or operator pushes manually. As of this entry
mainis ahead by 7 unpushed commits (packages embed, call/0003, CLAUDE.md, two memory entries, the earlier verify-sweep entry, and the three-persona commit).
2026-07-03 — resolved: push as connollydavid via a one-shot gh credential
- Operator chose to push as the owner. The working recipe:
gh auth switch -u connollydavid, thengit -c credential.helper= -c credential.helper='!gh auth git-credential' push origin main. The emptycredential.helper=resets the inheritedstorehelper (which holds slartibardfast's read-only token and 403s), and the gh helper then supplies connollydavid's token for that one push, with no change to global git config. - All pending commits landed (
23bdc1f..6f1e66a);mainis in sync with origin. - Going forward: repeat that one-shot override, or run
gh auth setup-gitonce (with connollydavid active) and clear the stale store token so a plaingit pushworks. slartibardfast stays read-only on the repo (not a collaborator); grant it write only if a bot-identity push is wanted later. This supersedes the earlier "push auth is flaky" framing: the cause was permissions plus a static store token, not flakiness.
2026-07-03 — published the book to GitHub Pages
- Ran the publish phase.
host-lifecycle book .(v0.35.1) writes book.toml withsrc = mdBook/srcandbuild-dir = mdBook/out, a SUMMARY in lifecycle order, and 17 pages;book --check .passes (every room renders: cast 5, plan 1, software 1, call 4, reference 4, memory 1). book.toml and mdBook/ are gitignored generated output. - Fixed two defects in the reference doc-site workflow. It was named
site.yml, but the publish-phase recheck istest -f .github/workflows/mdbook.yml, so adonepublish receipt would HAZARD until the file is namedmdbook.yml. It also published./book, while v0.35.1 builds tomdBook/out. Renamed to mdbook.yml, setpublish_dir: ./mdBook/out, and corrected the stalesrc=docscomment. Both look like template bugs at revision 565410a, worth proposing upstream. - CI (
mdbook.yml, on push to main) builds with mdbook v0.4.40 and deploys mdBook/out to thegh-pagesbranch via peaceiris/actions-gh-pages. GitHub Pages was disabled (the/pagesAPI returned 404), so enabled it from gh-pages root withgh api -X POST repos/connollydavid/agentic-UDPspeeder-simd/pages -f source[branch]=gh-pages -f source[path]=/. The site is live at https://connollydavid.github.io/agentic-UDPspeeder-simd/ (HTTP 200). - Publish receipt flipped from skip to done;
software --checkis green (the recheck now finds mdbook.yml). Verified locally with the pinned mdbook v0.4.40 before pushing.
2026-07-03 — udpspeeder-simd package builds clean against the x86_64 SDK
- SDK approach pivoted.
make worldwas excessive; used the public prebuilt x86_64 snapshot SDK (openwrt-sdk-x86-64_gcc-14.4.0_musl, the one OpenWrt CI uses) rather than a from-source build. The embeddedopenwrtcomponent stays but is not needed for this path and adds ~2.5 min tosoftware --check(flagged for the operator to keep or remove). - OpenWrt needs a case-sensitive FS: /mnt/c is case-insensitive and the build refuses it, so software/openwrt is symlinked to ext4 (/home/dconnolly/host-stores); the SDK and its build_dir also live on ext4.
- The package (net/udpspeeder-simd, authored in the packages worktree) built cleanly:
make package/udpspeeder-simd/compileproduced udpspeeder-simd-2026.07.03~3374e3bb-r1.apk (snapshot uses .apk, not .ipk). No fork change was needed — the udpspeeder-style cc_cross- gitversion Build/Prepare sufficed, and the makefile's
export STAGING_DIR=/tmp/did not break it. Dynamic linking confirmed by deps libc/libstdcpp6/librt/libatomic1. PKG_MIRROR_HASH = 225c0fef087e56c190ec1a626fc244a0e32f1f791d5e22efe604e962026c4698.
- gitversion Build/Prepare sufficed, and the makefile's
- Task receipts done: #fork-build, #feed-package, #service-integration. Pending: #test-script (CI runs it), #ci-sdk-lane, #interop, #pr, #backports. The package files are authored in the packages worktree but NOT yet committed or pushed to the fork; the branch strategy and the outward push/PR await operator go.
2026-07-03 — udpspeeder-simd pushed to the fork on a feature branch (PR held)
- Operator chose fork + PR, but the PR needs a review round, so it is NOT opened. Committed
net/udpspeeder-simd/ (Makefile + files + test.sh) on branch
udpspeeder-simdof connollydavid/packages, commit db858a8, pushed. Author + DCO Signed-off-by = David Connolly david@connol.ly (real name + public email, no Claude trailer, per call/0003 + OpenWrt rules). Subject "udpspeeder-simd: add package"; body references https://github.com/openwrt/packages/issues/28562. - The packages worktree had no git identity; set it to David Connolly / david@connol.ly. The canonical master worktree was used in-place to make the branch then restored to master @ 1d40ad9, so the .host-software pin and software --check stay intact; the feature branch lives on the fork, not as a host-tracked worktree.
- Open the PR (after review) from https://github.com/openwrt/packages/compare/master...connollydavid:packages:udpspeeder-simd?expand=1 Before submitting, consider syncing the fork onto current openwrt/packages master (the branch is based on the fork's master 1d40ad9, which may be behind upstream).
- openwrt embed KEPT (operator decision), despite the ~2.5 min software --check cost.
- Receipts: #fork-build/#feed-package/#service-integration/#interop done; #pr held (review round); #test-script + #ci-sdk-lane pending (CI); #backports policy-constrained.
- Update: rebased the branch onto current upstream openwrt/packages master (333bf60); tip is now a7f0f087, a single commit with no merge commits in the PR range; force-pushed to the fork. Formalities self-check (against .github/formalities.json) passes: crlf=0, subject 28<=60, body<=79, DCO Signed-off-by matches a non-noreply author, conffiles declared, openwrt-meta present, no patches. Open review-round items: Run Testing details (built x86_64 via the snapshot SDK gcc-14.4.0 + CI test.sh; a physical-device run is optional but stronger), and a release-notes line if the formalities bot requests one.
2026-07-03 — PR opened to openwrt/packages (pull/29901)
- Opened https://github.com/openwrt/packages/pull/29901 ("udpspeeder-simd: add package") as connollydavid, from connollydavid:udpspeeder-simd to openwrt:master, referencing the issue. Confirmed host-side clean first: main == origin, fork branch a7f0f087 synced, four receipts done. Release notes are in the PR body (a section), testing framed as x86_64 build + CI test.sh (operator accepted that trade-off).
- Corrected the plan: #pr no longer depends on #ci-sdk-lane. The operator directed opening the PR against OpenWrt's own CI, so the fork-side SDK CI lane is independent, not a prerequisite. Recorded #test-script done (script added + wired; the PR CI exercises it) and #pr done. Pending: #ci-sdk-lane (fork-side, optional) and #backports (policy: new packages are not normally backported to release branches).
- Next: watch the PR's FormalityCheck + multi-arch build. If the bot wants release notes in a different form, amend and force-push the fork branch.
2026-07-03 — addressed the Copilot review; fork version-banner fix; idiomatic test.sh
- Copilot flagged two on PR 29901, both valid: test.sh could false-positive (its grep matched
the program name, which a dynamic-loader error echoes), and the init had
fix_latencynon-local plus a duplicatedsock_buf. Both init issues were inherited verbatim from net/udpspeeder (the sibling has the same bugs). @codemarauder is Nishant Sharma, the net/udpspeeder maintainer; offered him co/primary maintainership of udpspeeder-simd in the PR comment. - Idiomatic gap and its fix: OpenWrt test.sh idiomatically greps the package version, but the
binary's print_help() truncated gitversion to 10 chars (
strncpy(...,10)), so a full-version grep would miss. Fixed the fork (main.cpp copies the full string; commit 2e12e5b pushed to branch_libev), re-pinned .host-software to 2e12e5b, updated the package (PKG_SOURCE_VERSION 2e12e5b, PKG_MIRROR_HASH 6d4564dc, PKG_VERSION now 2026.07.03~2e12e5b4), and switched test.sh togrep -qF "$PKG_VERSION". Amended the single PR commit (7f5bc7c8), force-pushed; PR still discrete (4 files). Posted a terse reply comment (#issuecomment-4877656043). - Rebuilt via the SDK (exit 0) for the new hash. The runtime version-grep is confirmed by OpenWrt's PR CI, since the cross-built musl binary cannot run on this host.
- Scope decision: we will NOT touch net/udpspeeder (operator). Removed the "happy to send a separate PR" offer from the PR comment (edited #issuecomment-4877656043); the sibling's same two init bugs are left alone.
2026-07-03 — OpenWrt PR #29901: 32-bit ARM build fix in the fork
- The PR's multi-arch CI (openwrt shared
Feeds Package Test Build) ran and failed only onarm_cortex-a15_neon-vfpv4andarm_cortex-a9_vfpv3-d16; aarch64, mips_24kc, x86_64, i386, powerpc, riscv64 all passed. Root cause was in the fork source, not the package:xor_spe.Smarked the stack non-exec with.section .note.GNU-stack,"",@progbits. On 32-bit ARM@starts a line comment, so gas ate@progbitsand reported "junk at end of line, first unrecognized character is,". aarch64/mips/ppc/riscv/x86 do not use@as a comment char, which is why only 32-bit ARM broke. - Fix (universal, per call/0005 — build adjustments live in the fork): spell it
%progbits. gas accepts%for the ELF section type on every target (verified locally: x86 g++ assembles both@progbitsand%progbits), and%is the required form where@is a comment. Fork commitc2b3759onbranch_libev. - Propagation: re-pinned
.host-softwareudpspeeder-simd → c2b3759; bumped the PR'snet/udpspeeder-simd/MakefilePKG_SOURCE_VERSION → c2b37590 and refreshed PKG_MIRROR_HASH → d6f564823fa0217788bc937e06c71266bfa3a457d0803912b2d10bcb9957d3ba (amended the single PR commit, force-pushed; PR head 5d8ed050). Kept the fork commit's Claude co-author trailer; the packages commit has none (call/0003). - Mirror-hash regen recipe (local x86_64 SDK): set PKG_MIRROR_HASH to 64 zeros, run
make package/<pkg>/download V=s; the download clones, packs the reproducible git tarball, and printsgot <real-hash>.skipdoes NOT work for a git-proto source (the github archive downloader refuses without a real sha256). Then compile to confirm.
2026-07-17 — OpenWrt PR #30015 (net/udpspeeder) + timeout fold into PR #29901
- Opened https://github.com/openwrt/packages/pull/30015 (net/udpspeeder) against
openwrt/packages:master from connollydavid/packages:udpspeeder-toolchain-flags. Two atomic
commits, PKG_RELEASE 3→4→5:
udpspeeder: build with the toolchain CXX and flags: new patch 010-build-with-toolchain-flags.patch builds the cross target with $(CXX) and honours $(CXXFLAGS)/$(LDFLAGS), makes gitversion overridable; package drops the Build/Prepare sed hacks, injects the version via MAKE_FLAGS, adds PKG_BUILD_FLAGS:=no-mips16.udpspeeder: pass the timeout option to the binary: addsprocd_append_param command --timeout "${timeout}"after --mtu in files/udpspeeder-init.Fixes: .../issues/18955auto-closes on merge. The option was validated and shipped in the sample config (tunnel2) but never passed to the binary.
- no-mips16 is REQUIRED, not cosmetic: honouring CXXFLAGS pulls -mips16 onto
mips_24kc/mipsel_24kc, where C++ std::atomic emits a
syncbarrier MIPS16 cannot encode ("opcode not supported on this processor: mips2 (mips2) 'sync'"); no-mips16 strips it (MIPS32). - Non-drift confirmed from binary help:
--timeout ... unit: ms, default: 8ms; UCI schema default is 8, so wiring it is a no-op at the default and just makes the advertised option work. - Verified locally via snapshot SDK on all ten CI arches (scratchpad/build_all.sh): all pass; both mips controls (without no-mips16) fail on the sync opcode, proving necessity.
- PR #29901 (net/udpspeeder-simd) had the identical latent timeout bug. Folded the same
--timeoutline into the singleudpspeeder-simd: add packagecommit (amend, NO PKG_RELEASE bump since 1.0.0-1 is unreleased); force-pushed connollydavid:udpspeeder-simd → 3540c71ce. Posted a terse alignment comment (#issuecomment-5002468098) referencing #30015. - Hygiene: packages commits carry NO Claude trailer (call/0003); sign-off = author = David Connolly david@connol.ly. #30015 rebased onto current upstream/master (net/udpspeeder untouched upstream); #29901 left on its older base (PR diff is clean vs merge-base and CI builds on the snapshot SDK, so no rebase needed and none requested). .host-software NOT re-pinned (feature-branch work; the master pin is unchanged).
2026-07-17: #30015 "Dirty patches detected" — patches must be quilt-refreshed
- All ten Feeds Package Test Build jobs failed at
make package/udpspeeder/refresh: the CI requires each patch byte-identical to quilt's canonical output. Our patch applied at exact offsets with no fuzz, yet still failed. - Root cause: OpenWrt refreshes with
QUILT_DIFF_OPTS="-p" quilt --quiltrc=- refresh -p ab --no-index --no-timestamps(openwrt/openwrt include/quilt.mk), and GNU diff-pappends function context to hunk headers (@@ -51,7 +51,7 @@ cygwin:git_version).git format-patchoutput lacks the suffix, so the refresh rewrites the file and the git-diff check trips. - Fix recipe: extract the pinned source (
git archive 20230206.0), set uppatches/+ series,quilt --quiltrc=- push -a, run the refresh command above, copy the refreshed patch back. Verified idempotent: a second refresh reports "is unchanged". Quilt preserves the git header (From/Subject/Signed-off-by/diffstat) untouched. - Folded into the toolchain commit via
commit --fixup+GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash; range-diff showed only the two hunk-header lines changed, timeout commit byte-identical (=). Force-pushed7a76af810...075f4a7e0(lease + force-if-includes). - Lesson: any patch destined for openwrt/packages should be generated or round-tripped through quilt with those exact args before committing, not taken raw from git format-patch.
2026-07-31 — upstream merged our toolchain-flags PR; taking it in both packages
- wangyu-/UDPspeeder merged https://github.com/wangyu-/UDPspeeder/pull/356 (686d6079), then
reshaped it in b6a1b594:
crosswas restored to the hardcoded${cc_cross}, and the change landed as a NEW target instead:cross_cxx: ${CXX} -o ${NAME}_cross -I. ${SOURCES} ${FLAGS} -O2 ${CXXFLAGS} ${LDFLAGS} ${LDLIBS}. Three deltas vs what we sent:-O2moved BEFORE${CXXFLAGS}(so a caller's -Os wins instead of being overridden),-lrtdropped,${LDLIBS}added. Ourgitversion ?=hunk survived verbatim, so MAKE_FLAGS version injection still works. - Consequence for the merged net/udpspeeder patch: its second hunk (the
gitversion ?=line) can no longer apply to any source at or past b6a1b594, since upstream already has it. So the patch had to be dropped as part of a version bump, not deferred. -lrtis a no-op on OpenWrt, PROVEN not assumed: musl'slibrt.ain the toolchain is an 8-byte empty archive (clock_gettime is in libc), and the packaged binary built withLDLIBS="-lrt"and without it are byte-identical (sha256 502163f5...). NEEDED is libstdc++/libgcc_s/libc either way. So neither package passes -lrt; a toolchain that needs it can pass LDLIBS. LeftDEPENDS:=+librtalone in both (inert: musl's libc provides it) rather than widen the diff.- The 3-year source bump 20230206.0 -> b6a1b594 is WIRE-SAFE, also proven: the only functional
change is swapping the in-tree
crc32hfor Stephan Brumme'scrc32_fast. Both are standard zlib CRC-32 (poly 0xEDB88320, init ~0, final complement); a comparison harness over every length 0..2048 x 8 random buffers matched exactly, and both give 0xCBF43926 for "123456789". The rest is typo fixes, anis_vaild->is_validrename, and a djb2/sdbm loop rewrite that removes a one-byte over-read without changing the hash. - OpenWrt version convention for an untagged git source is
<base>~<shortsha>+ a full-sha PKG_SOURCE_VERSION. Careful:~sorts BEFORE end-of-string (Debian style), so20230206.0~<sha>would sort BELOW the released 20230206.0 and break the upgrade path. Used20260731~b6a1b594(commit date as base) so it sorts above. - Fork: added the same
cross_cxxtarget (commit 81fde6c, tagged v1.0.1, pushed); kept our cross/cross2/cross3 as-is since this fork deliberately dropped the hardcoded compiler paths. Re-pinned .host-software to 81fde6c.
2026-07-31 — correction: PR #29901 has no test.sh (the 2026-07-03 entry is wrong)
- The 2026-07-03 entry above says "#test-script done ... test.sh added beside the Makefile;
exercised by the OpenWrt PR CI runtime test", and
.host-task-receiptsrecords the same evidence forplan/0000#test-script. Both are FALSE as of now, and have been since 2026-07-04. - Traced through the branch reflog: test.sh survived to b7ef6490 (2026-07-04 21:31) and was gone
in the very next amend, b9e331ec (2026-07-04 23:16), the one that fixed the Build/Prepare
double-expansion. It was dropped accidentally, not by decision, and every later force-push
carried the loss forward. The PR head has carried three files (Makefile, config, init) ever
since, so the CI arches flagged
runtime_test: truehave had nothing to run. - Operator decision on 2026-07-31: LEAVE IT OUT rather than restore it, so the package ships
without a runtime test. BKPepe had questioned the script anyway ("Do we need this script at
all? This should be covered by generic testing"). The
#test-scriptreceipt is therefore stale and should be re-dispositioned by the operator (it is a tool-written ledger; not hand-edited here). - Lesson: an amend that fixes one file can silently drop another. Diff the file LIST against the PR head after every amend + force-push, not just the file contents.
2026-07-31 — OpenWrt packaging idioms, and how to test under qemu properly
- VERSION FORM for an untagged git source: do NOT hand-write PKG_VERSION. Set PKG_SOURCE_DATE
plus a full-sha PKG_SOURCE_VERSION and let include/download.mk derive it:
PKG_VERSION := $(subst -,.,$(PKG_SOURCE_DATE))~$(call version_abbrev,$(PKG_SOURCE_VERSION)), giving2026.07.31~b6a1b594(version_abbrev = 8 chars; it returns the FULL sha under DUMP, so the feed index shows the long form while the build uses the short one). Feed usage: 96 packages use PKG_SOURCE_DATE vs 3 that hand-write a~version. Changing the version form changes PKG_MIRROR_HASH, since the tarball's inner subdir is $(PKG_NAME)-$(PKG_VERSION). - UPSTREAM UDPspeeder TRUNCATES THE VERSION: print_help does
strncpy(buf, gitversion, 10), so--help(the flag OpenWrt's generic check finds) shows ten characters only. The old version20230206.0was ten characters exactly, so the check passed by luck for years. Any longer version needstest-version.sh, the idiomatic override (70 in the feed). Our simd fork already prints the full version, so only net/udpspeeder needs the override. - DEPS ARE DERIVED FROM THE ELF: include/package-pack.mk runs scripts/gen-dependencies.sh, which
reads NEEDED and FAILS the build for a linked library that is not declared. It never checks for
extras, so a declared-but-unlinked dep is inert. librt and libatomic were both provably inert:
70/70 builds and packet tests across all 35 published architectures with both removed, no binary
NEEDs either, and no
__atomic_*/__sync_*symbol anywhere. On musl, Package/librt/install copies nothing at all (ifneq ($(CONFIG_USE_MUSL),y)). - RUNTIME TESTS, the idiom:
test.sh <pkgname> <version>runs INSIDE a target-arch container (binfmt + qemu-user-static) with the package installed.pre-test.shinstalls test deps (apk add socat). A package-specific test.sh REPLACES the generic tests (version check included), and test-version.sh is only consulted by the generic path, so test.sh must assert the version itself. BusyBox in OpenWrt is built with NC_SERVER=n and NC_EXTRA=n, so nc can neither listen nor exec: use socat via pre-test.sh. Do not use a fixed sleep, the container is emulated; retry the payload instead. - QEMU CPU MODELS, do not trust the default. powerpc_8548 (CPU_TYPE 8548, e500v2) fails under the
default 32-bit PPC model with an illegal instruction, and it is NOT the package: the faulting
word disassembles to
iseleqinside musl libc, which holds 63 isel instructions while our binaries hold none.-cpu e500v2_v10runs it. Control: the musl loader alone faults under the default and prints its version under e500v2_v10. Every arch now names a verified model in .github/scripts/arch-map.tsv; where qemu-user lacks the exact CPU the closest SUPERSET is used (xscale and fa526 -> arm926, cortex-a5 -> cortex-a7, pentium-mmx -> pentium2, pentium4 -> n270, since a weaker model invents failures and a richer one hides them and skews feature detection). - PROBE BY OUTPUT, NOT EXIT STATUS: musl's loader prints its version and exits 1. A first pass at CPU discovery keyed on exit status and reported every architecture broken. Judge the output.
- SCOPE: OpenWrt's CI runtime-tests only aarch64_generic, arm_cortex-a15_neon-vfpv4, i386_pentium-mmx, mips_24kc and x86_64; powerpc_8548 is runtime_test:false. So the CPU-model work is OUR local lane's concern, not something the OpenWrt PR depends on.
- arm_fa526 (gemini) is BUILD-ONLY under qemu-user, and the cause is the emulator, not the
package. That target compiles plain ARMv4: its binary holds ZERO bx/blx, where the ARMv5 build
of the same package holds 133, so it cannot enter Thumb code at all. qemu-user always maps a
Thumb vdso, so the call to __vdso_gettimeofday runs Thumb bytes in ARM state and branches to a
wild address; the in_asm trace ends in
IN: __vdso_gettimeofdaydecoding 46c046c0 (Thumb nop;nop) as ARM and branching to 0x40b2838c, exactly the SIGSEGV si_addr. Proven not ours two ways: the CURRENTLY SHIPPED 20230206.0 package fails identically under -cpu arm926, and a trivial C++ program against the same musl and libstdc++ runs fine there. Real hardware never meets it, since the Linux ARM vdso needs ARMv7 and a gemini kernel supplies none, leaving musl on plain syscalls. qemu-user has no flag to withhold the vdso, so the packet test is skipped for that arch rather than run under a v7 model that would hide real mismatches.
2026-07-31 — udpspeeder-simd crashed on x86_64 without SSSE3 (found by faithful qemu models)
- lib/fec.cpp initialised
addmul1_x86_fn = addmul1_ssse3and only ever RAISED it to AVX2 or AVX-512. Nothing probed SSSE3, and x86_64 does NOT imply it: AMD K8/K10 (Athlon 64, Opteron, Phenom, Phenom II, Athlon II) lack it, as do early 64-bit Intel parts; AMD added it with Bulldozer in 2011. Those CPUs took SIGILL on the first PSHUFB in the Reed-Solomon inner loop, so the tunnel died on its first FEC batch. Upstream udpspeeder was unaffected. - Only visible under a faithful CPU model. Every earlier sweep used qemu's DEFAULT x86_64 model
(rich), and OpenWrt's own CI runtime-tests x86_64 on a GitHub runner that has SSSE3/AVX2, so the
bad path never ran. It appeared the moment the map named
-cpu qemu64(the AMD64 baseline). - Fix (fork v1.0.2, f3834047): pointer starts at scalar and rises only as CPUID allows, via
cpu_has_ssse3() (leaf 1 ECX bit 9) and cpu_has_sse2(); new SSE2 addmul1 sits beneath SSSE3, so a
CPU without PSHUFB still vectorises. SSE2 has no byte shuffle, so it multiplies by repeated
doubling:
_mm_add_epi8(x,x)for the shift and_mm_cmpgt_epi8(0,x)to select the reduction. The reduction constant is read fromgf_mul_table[2][0x80], not hardcoded, so it follows the field. Measured 1.73x scalar at 1500B (913 vs 1579 ns); ssse3 is 9.8x. - Also hardened packet_cook.cpp: its AVX2 tier was set from CPUID leaf 7 EBX bit 5 ALONE, with no OSXSAVE/XCR0 check and no max-leaf check. Now mirrors the correct probe.
- Test method worth reusing:
bench_addmul1_force(name)pins one implementation so test_fec can hold EVERY path against scalar over all 256 multipliers and sizes hitting each loop and tail. Ran the suite underqemu-x86_64 -cpu qemu64|Nehalem|Haswellto exercise sse2/ssse3/avx2. AVX-512BW cannot be verified this way: qemu-user leaves ZMM state disabled in XCR0, so the OS-support check correctly declines it. - HARNESS BUG worth remembering:
find ... | head -1picked a STALE build dir from an earlier session in a reused SDK, so one x86_64 result described a binary nobody had built that run. Select by newest mtime (-printf '%T@ %p'| sort -rn). Fresh CI SDKs never hit this; local reused SDKs do.
2026-07-31 — qemu64 is not the x86_64 floor; the first Opteron is
Asked whether we are aligned with the very first x86_64 chip, and the honest answer needed evidence at both ends rather than an assertion. Findings worth keeping:
-cpu qemu64GRANTS SSE3. It caught the SSSE3 crash, but it is not the architectural floor and never was. The floor is-cpu Opteron_G1,-sse3: qemu's model of the AMD Opteron 240 of 2003, the first x86_64 silicon, with SSE3 subtracted for the earliest stepping. SSE3 only arrived on K8 revision E in 2005. The arch map's x86_64 row now names that model.- Compile-time floor is already right: the OpenWrt x86_64 toolchain defaults to
-march=x86-64with sse3, ssse3, sse4, cx16 and popcnt all DISABLED (gcc -Q --help=targetproves it, andCONFIG_TARGET_OPTIMIZATIONis only-Os -pipe, so nothing raises it). XGETBVIS NOT BASELINE. It arrived with XSAVE in 2008, so a K8 faults on it. Our probes check CPUID leaf 1 ECX bit 27 (OSXSAVE) first, and the EMITTED code keeps the order: every xgetbv sits behindbt $0x1b,%ecx/jae. Verified in the disassembly, not just the source. Worth re-checking after any compiler bump, since the guard is a branch the optimiser could in principle move.- CRC32C's hardware path is behind
crc32c_has_hw()(leaf 1 ECX bit 20, SSE4.2) and falls back to the slicing-by-8 table, so it is safe on K8 too. - Static screen worth reusing, the x86 analogue of isa-check.sh: disassemble and list every
mnemonic outside the baseline together with its containing function.
udpspeederholds ZERO;udpspeeder-simdconfines all of them toaddmul1_{ssse3,avx2,avx512},xor_tile_{avx2,avx512}andcrc32c_hw, each reached only through CPUID. That is the property to hold, not "it ran". - Dynamic proof: both packages pass the packet test under
Opteron_G1andOpteron_G1,-sse3, and test_udpspeeder under those models selects sse2 and agrees with scalar over all 256 multipliers, declining ssse3/avx2/avx512bw. - Reading tests through a grep can invent a hollow section: filtering on "addmul1" hid the tier results, whose pass lines read "sse2 agrees with scalar". Read the section, not a keyword.
2026-07-31 — the x86 lacuna: OpenWrt has three x86 package arches, not one
Asked whether x86 coverage is complete. It was not, and the gap was in the emulation models rather than in the architecture list.
- OpenWrt publishes FOUR x86 subtargets (64, generic, geode, legacy) but THREE package
architectures. geode and legacy declare no CPU_TYPE, so both fall to the i386 default of
pentium-mmx(include/target.mk). One row covers the pair; generic is pentium4; 64 is x86_64. - BOTH i386 models were too rich, the same failure as qemu64 on x86_64:
n270(Atom) HAS SSSE3, so the i386_pentium4 job selected the SSSE3 path and never once ran the SSE2 tier a real Pentium 4 uses. Nowpentium3,+sse2, which is exactly a P4's ISA.pentium2HAS CMOV, which a Geode GX/LX and a Pentium MMX do not. Nowpentium,+mmx.
- qemu-user genuinely gates these, proven by one-instruction controls rather than assumed:
pentium2runs cmov and faults on pshufb;pentium2,-cmovandpentium,+mmxfault on cmov;n270runs BOTH cmov and pshufb;pentium3,+sse2runs movdqa and faults on movddup. Feature subtraction/addition on a stock model works, so compose the model instead of borrowing one. - Static screen of the geode build: nothing above the floor outside CPUID-guarded functions. The
one CMOV lives inside
addmul1_ssse3, which GCC is free to emit becausetarget("ssse3")implies P6, and it only ever runs on a CPU that has SSSE3 and therefore CMOV. Not a defect. - All three x86 arches pass both packages under the tightened models.
mips_4kecandriscv64_riscv64DO have package directories on the buildbot, so the listing alone suggests 37 arches. They are frozen: last built April and July of 2025 against zlib 1.3.1, where a live arch carries a build from this month. Check the DATES, not the directory.
2026-07-31 — MMX helps the XOR and hurts the multiply; measure per path
"MMX is still SIMD" — and OpenWrt's lowest x86 arch (i386_pentium-mmx, from geode and legacy) has MMX and nothing above it. So the fork was taken down to that floor. Two opposite results, and the lesson is that "add a lower SIMD tier" is not one decision but one per kernel.
- MMX addmul1: WRITTEN, MEASURED, REVERTED. The repeated-doubling multiply costs ~5 ops/byte at MMX's 8-byte width, against ONE L1-resident load per byte for scalar, which indexes a single 256-byte row of gf_mul_table for a fixed c. Measured on a real 32-bit build: mmx 0.55x scalar (3166 vs 1738 ns at 1500B); sse2 1.70x. SSE2 only wins by doubling the width. The floor keeps the scalar table, which is correct AND faster.
- MMX xor_tile: KEPT. XOR is one op per width, so width converts straight into throughput. Against the four-byte word path i386 actually had: mmx 1.46x, sse2 3.08x.
- The bigger find: packet_cook.cpp's ENTIRE x86 SIMD block was
#if defined(__x86_64__), so every 32-bit build used the 4-byte word loop — including i386_pentium4, which has SSE2. x86/generic is a commonly used target, so that was a 3.08x left on the floor for years. fec.cpp had already been widened to i386; packet_cook.cpp had not. When widening one file's ISA guards, grep for the same guard elsewhere. - MEASURE 32-BIT MMX ON A 32-BIT BUILD. On x86_64 GCC emulates __m64 with SSE (TARGET_MMX_WITH_SSE), so an x86_64 measurement of MMX is not MMX. Build with the OpenWrt i386 toolchain and run the binary natively (WSL2 runs i386 ELF fine); qemu timings are useless for ISA ratios.
- EMMS is mandatory: MMX registers alias the x87 stack and the tunnel does FP work in its timers.
- Renaming a force() tier label silently turns the reference call into a no-op, so the test compares a tier against itself and always passes. Renamed tier 0 "scalar"->"word" and had to fix the test's reference call in the same breath. A hollow green, exactly the shape call/0035 warns about.
Dispatch is a separate claim from implementation, and it needed its own lane
The tier comparisons pin a path and hold it against a reference. That says nothing about which path the dispatcher CHOOSES, and the choice is what the SSSE3 fault broke. A CI runner carries every feature, so it always chooses the top path; no runner-only job can see the bug class.
- Three runtime dispatchers exist, not one: addmul1 (fec.cpp), xor_tile (packet_cook.cpp) and crc32c (crc32c.h, an SSE4.2 probe resolving a function pointer on first call). All three now report their choice through bench_*_auto() and are held to EXPECT_ADDMUL1 / EXPECT_XOR_TILE / EXPECT_CRC32C.
- The auto hook must RE-DERIVE, not read back. bench_addmul1_force() overwrites the pointer, so the reporter re-runs the real selection (addmul1_select(), extracted from init_fec) and the selection now resets to scalar first rather than only raising. Reading the pointer back would report whatever a previous test pinned.
- QEMU TCG IMPLEMENTS NO AVX-512 AT ANY CPU MODEL (checked on qemu 11.0.2): -cpu Skylake-Server warns "TCG doesn't support requested feature ... avx512bw", clears the CPUID bit and leaves XCR0 at 0x207 rather than 0xE6. So the AVX-512 path cannot be exercised under emulation at all, on any model, ever. That row instead proves the gate DECLINES on a part whose model name says otherwise. AVX-512BW correctness is verified only when a GitHub runner happens to land on an Intel host.
- The OpenWrt i386 toolchains default to -march=i486, so the march must be named explicitly to reproduce what OpenWrt ships (pentium-mmx for geode/legacy, pentium4 for generic).
- A pentium-mmx-built binary runs clean on qemu's 486 model and correctly selects scalar/word/sw, so the bottom rung of the ladder is reachable and tested even below OpenWrt's own floor.
- The Makefile forces
export STAGING_DIR=/tmp/, which overrides the environment, so the OpenWrt toolchain wrapper only warns and no STAGING_DIR needs setting for amake test-crossbuild. It matters only when invoking the cross gcc directly.
The SPE XOR was never shipped, and the package could not have told us
The e500v2 SPE unit does the tile XOR 64 bits at a time. That path needs SPE=1 on the make line,
and net/udpspeeder-simd passes only cross_cxx gitversion=..., so every mpc85xx build ever
shipped has used the word path. Confirmed by objdump: with the flag, 20 SPE opcodes; without it, 0.
- GCC defines no e500 macro. GCC removed SPE support, which is why the path is a hand-written
xor_spe.Splus-Wa,-msperather than intrinsics. Diffing the predefines of-mcpu=8548against-mcpu=464fpyields only soft-float proxies (__NO_FPRS__,_SOFT_FLOAT,_SOFT_DOUBLE,__NO_LWSYNC__). Keying on those would shipevxorto a soft-float classic PowerPC and fault, the SSSE3 bug again. So auto-detection from the compiler is not available. - The target says it instead, and the software can read it. OpenWrt's
MAKE_VARSexportsCXXFLAGS="$(TARGET_CXXFLAGS) ...",TARGET_CXXFLAGS = TARGET_CFLAGS, and that carriesCPU_CFLAGS_$(CPU_TYPE), i.e.-mcpu=8548. So the makefile can read the core from the flags it is already handed, and the package needs no CPU conditional. Note the fork's own cross targets take the march insideCC, notCXXFLAGS, so the filter searchesCC CXX CFLAGS CXXFLAGS. - 8548 alone is too narrow. OpenWrt maps these 32-bit PowerPC types: 603e, 8540, 8548, 405, 440,
464fp. 8540 is e500v1 and also has SPE (v1 lacks only double-precision SPE FP, which this does
not use). No published target uses 8540 today. Match both, with
filter(whole words) notfindstring(substring). - The XOR round-trip cannot catch a wrong XOR, only a missing one. It asserts
changed && restored. XOR is its own inverse, so a wrong-but-deterministic path still restores; a deliberate truncation was caught in exactly the degenerate cases where the XOR did nothing at all. The differential comparison against the word reference is what actually bites, verified by truncating an SPE call and watching that test alone fail across every tile, length and offset. - The sweep's spe row is red until the package bumps.
arch-map.tsvnow asserts SPE opcodes for powerpc_8548, but the sweep builds from the package's pinnedPKG_VERSION. It passes only once the package points at a fork release carrying the makefile detection, i.e. the pending v1.0.3 bump.
The aarch64 gap was ours alone, and the pins needed a negative control
Closing the NEON and ARM-CRC32 gaps required no OpenWrt change at all. Proof: building the fork
with the airoha (cortex-a53) toolchain and that target's own -mcpu yields 6 crc32c* instructions
and 9 eor v*.16b NEON ops. The code already ships; only the tests were absent.
__ARM_FEATURE_CRC32needs a named core. Measured across the OpenWrt aarch64 toolchains:-mcpu=cortex-a53,-a72and-a76define it,-mcpu=generic(armsr/armv8,CONFIG_CPU_TYPEgeneric) does not. So three of the four published aarch64 targets ship the hardware checksum. Debian'saarch64-linux-gnu-g++defaults to no core, which is why fork CI compiled the path out and[CRC32C hw vs sw agreement]skipped silently. CI now also builds-mcpu=cortex-a53and requires the comparison to have run, since a skip must not read as a pass.- A pin that does not switch is a hollow green.
bench_*_force()returning 1 while the path stays the same makes the reference and the candidate the same code, so the comparison passes vacuously. Both aarch64 pins were checked by deliberately faultingaddmul1_neonand the NEONxor_tileand confirming each named test fails. Never trust a new comparison without that control. - The tier tests used to leave the reference pinned. They ended with
force("scalar")/force("word"), so every round-trip test after them ran the reference path, not the dispatched one. Now they end withbench_*_auto(). This was already wrong on x86 and would have silently removed NEON from the aarch64 round-trips. - qemu-aarch64
-cpu cortex-a53does implement CRC32, unlike the AVX-512 case, so this gap is fully closable under emulation.