# G-172 Distributed binaries declare a CPU floor - no shipped artifact is tuned to its build host Status: achieved 2026-08-07 Scope: src/buildsuites/suite_tcl90/build905.zig and src/buildsuites/suite_tcl86/build86.zig (flagless target/cpu resolution defaults); src/tools/punkzip/build.zig and src/tools/punkres/build.zig (distributed build-path tools); src/buildsuites/suite_tcl90/tools/family_artifacts.tcl (artifact record emission - the recorded floor field); punkbin artifact repo (external c:/repo/jn/punkbin - win32-x86_64/*.toml sidecars, defaults.txt curation); bin/ (the shipped zig-built exes); src/scriptapps/bin/punk-runtime.* (floor reporting on fetch, floor-vs-host verdict on use/run); scriptlib/developer/ (cpu-floor audit tool) Goal: Every binary this project distributes runs on any CPU meeting a declared instruction-set floor - the floor is a property of the build recipe rather than of whichever machine happened to run it, it travels with the artifact in its metadata record, and neither a build nor a publication can silently ship host-tuned code. Acceptance: A flagless build of each distributed-artifact recipe (suite_tcl90, suite_tcl86, punkzip, punkres) on an AVX-512-capable host emits zero instructions above the declared floor - verified by an audit tool that disassembles the produced binaries and reports out-of-floor instructions, carrying the current AVX-512 regression as its fixture; `-Dcpu=native` still yields a host-tuned local build, proving the floor is a default and not a restriction; each published artifact's metadata record states the floor it was built to and punk-runtime surfaces it; and selecting or launching a runtime whose recorded floor the local CPU does not meet produces a named diagnosis instead of a silent 0xC000001D, while fetching for any platform stays ungated. ## Context Reported 2026-08-06: on a freshly pulled checkout, `bin/punk-runtime.cmd fetch` with no runtime name retrieved the curated default `tclsh9.0.5-punk-r2.exe`, which exited immediately with no prompt, no error and no output. Two other runtimes fetched onto the same machine (`tclsh902z.exe`, `tclsfe-x64.exe`) started normally. The failure was initially read as a Windows-version difference (25H2 works, 23H2 does not). It is not an OS difference. Disassembling `.text` of the artifacts: | artifact | zmm sites | ymm sites | built by | |---|---|---|---| | `tclsh9.0.5-punk-r2.exe` | 2756 | 617 | zig 0.16.0, ReleaseFast | | `tclsh9.0.5-r2.exe` (plain) | 2756 | 617 | zig 0.16.0, ReleaseFast | | `tclsh902z.exe` | 0 | 0 | BAWT / MSYS2 mingw GCC | | `tclsfe-x64.exe` | 0 | 0 | apnadkarni MSVC | The 9.0.5 binaries carry real EVEX-encoded AVX-512 - `vmovdqu64`, `vpscatterqq`, `vpermt2q`, `vpmovm2q`, `vptestnmq`, `vshufi64x2` - with the first site at RVA 0x2360, the very front of `.text`. Tcl has no runtime CPU dispatch, so none of it is guarded. On a CPU without AVX-512 the first one raises #UD and the process is killed with STATUS_ILLEGAL_INSTRUCTION (`0xC000001D`, errorlevel -1073741795) before Tcl writes a byte. Confirmed against the reporting fleet - the split is exactly the AVX-512 line: | host | CPU | uarch | AVX-512 | 9.0.5 runtime | |---|---|---|---|---| | is-vmhost-1 (Win11 23H2) | TR PRO 3955WX | Zen 2 | no | dies, errorlevel -1073741795 | | jcross1 (Win11) | Ryzen 5 7600X | Zen 4 | yes | works | | build/dev box (Win11 25H2) | TR PRO 9955WX | Zen 5 | yes | works | Cause: the recipes called `b.standardTargetOptions(.{})` and no build path passed `-Dtarget`/`-Dcpu`. With no flag, zig resolves cpu_model `determined_by_arch_os` by NATIVE detection; on a Zen 5 host that is `znver5`, and `ReleaseFast` then auto-vectorises freely. Measured with the bundled 0.16.0-dev zig on an auto-vectorisable C loop: flagless -> `znver5`, zmm=805; `-Dcpu=native` -> `znver5`, zmm=805; `-Dtarget=x86_64-windows` -> `x86_64`, zmm=0; `-Dcpu=baseline` -> `x86_64`, zmm=0; `-Dcpu=x86_64_v2` -> `x86_64_v2`, zmm=0. The class is wider than the runtime family. Every zig-built binary the project distributes inherits its build host's instruction set: - all six published `tclsh9.0.5-*` artifacts (punk + plain, r1 + r2, both `-bi-` variants): 2756 zmm each - the whole family, so there is no 9.0.5 fallback to fetch instead; - `bin/punkzip.exe` 1724 zmm, `bin/punkres.exe` 1423 zmm - both build-path tools, so a `bake` on a non-AVX-512 machine dies too; - `bin/punk905.exe`, `punk9-dev.exe`, `punk9_beta.exe`, `punk9bi_beta.exe`, `punkdeclare.exe`, `punkfiledemo.exe`: 2756 zmm; - an older generation (`punk901*.exe`, `punk9.exe`, `xcritcl.exe`, `tclsh90s*.exe`, `mkzipfix.exe`) carries ~4500 ymm sites and no AVX-512 - AVX2-only, from an earlier build host. Same defect, milder floor. Two things make this worse than a portability nit. The artifact record already carries `toolchain`, `optimize` and six source-checkout digests but says nothing about the instruction set, so the one fact that determines whether the binary can execute is the one fact not recorded. And punkbin `defaults.txt` points `win32-x86_64` at `tclsh9.0.5-punk-r2.exe`, so a flagless fetch on a fresh checkout hands every non-AVX-512 machine a dead default runtime - the project's first contact with a new user fails with no message at all. ## Approach 1. Recipe default, not a build-command convention. `standardTargetOptions` takes `.{ .default_target = .{ .cpu_model = .baseline } }`, so a flagless build is portable and `-Dcpu=native` is the opt-in for a host-tuned local build. "Remember to pass `-Dcpu=` when publishing" is precisely what failed silently for six artifacts and four recipes; the default is what publishes. (Applied 2026-08-06 to all four recipes - see Progress.) 2. Floor choice: `baseline` (x86-64 v1) for the x86_64 family (user decision 2026-08-06). For a Tcl interpreter the codegen delta against v2/v3 is negligible against the cost of the default runtime failing to start. The win32-ix86 lane (G-130) makes the same choice on its own axis. 3. Record the floor in the artifact metadata: `[provenance]` gains the resolved cpu model and the floor the recipe declared, emitted by `family_artifacts.tcl` alongside `toolchain`/`optimize`, and carried in both the embedded record and the sidecar toml. 4. Audit tool in `scriptlib/developer/` (the advisory-tool tier): disassemble a binary, classify instructions against a named floor, report anything above it. The current AVX-512 artifacts are its fixture - the tool must flag them and must pass a rebuilt one. This is what makes the acceptance criterion measurable rather than a promise about build habits. 5. punk-runtime surfaces the floor, gating only where execution is implied. `fetch` stays ungated and reports the floor as information - fetching for another platform (`-platform

`) is a normal cross-machine workflow and a local-CPU verdict would be meaningless there (user point 2026-08-06). The actionable moment is selection and launch: `use` and `run` are already the local-only actions (`run` explicitly takes no `-platform` because "foreign binaries are not runnable here"), so that is where a floor the host does not meet becomes a named diagnosis. `list` annotates rows the local CPU cannot run. 6. Republication is IN-SITU replacement (user decision 2026-08-06): rebuilt binaries replace the unsuitable artifacts under their existing names and revisions, with sidecar tomls and `sha1sums.txt` regenerated to match. Artifact immutability remains the long-term intention but is explicitly violable during alpha - every consuming system is in-house, there are no third-party users, and leaving known-dead binaries fetchable is the worse outcome. No `defaults.txt` repoint is needed (it already names `tclsh9.0.5-punk-r2.exe`). Replacement may proceed as soon as a rebuilt binary passes the zmm audit; if the floor field (Approach 3) is not yet in the record schema, a second in-situ record refresh adds it later - cheap under the same dispensation. ## Alternatives considered - Pass `-Dcpu=baseline` at publish time only, leaving recipes native by default - rejected: it is the same unenforced convention that produced the defect, and it makes a developer's local build and the published artifact differ in a way nothing checks. - `x86_64_v2` or `x86_64_v3` floor - deferred, not rejected. v2 (SSE4.2/POPCNT, 2008+) excludes nothing realistically in service; v3 (AVX2/BMI2, 2013+) excludes Intel N-series/Pentium Silver, which have no AVX at all. Revisit per artifact class if a measured win justifies it; the recipe already accepts `-Dcpu=`. - Runtime CPU dispatch in the hot paths - rejected as out of proportion: Tcl upstream has none, and the goal is that a shipped binary starts everywhere, not that it is optimal everywhere. - Detect and report at first run instead of building portably - rejected: the failure is #UD before `main`, so there is no point at which our own code could report anything. ## Notes - Related: G-105 - cross-target builds parameterize the same recipe's target; this goal is the CPU axis of that surface. Whichever lands first, the other inherits the resolved default_target shape. - Related: G-130 - the win32-ix86 lane consumes the floor decision on its own axis (an i686 baseline floor is the same class of choice). - Related: G-116, G-108 - additional products of the same recipe (tcltls bi-family battery, the debug `-dbg` tier); both inherit the pin. - Related: G-142 - curated listing manifests are where a per-artifact floor becomes visible before download. - Related: G-147 - fetch-side sibling; a floor field joins the facts a revision check reads. - Related: G-137 - punkres RT_VERSION stamping consumes `bin/punkres.exe`, itself one of the affected binaries. - Related (archived): G-117 (self-describing runtimes - see goals/archive/G-117-self-describing-runtimes.md) and G-123 (runtime tiers, schema v2 - see goals/archive/G-123-thirdparty-runtime-tiers.md) - the artifact record schema this goal extends with the floor field. - Related (archived): G-126 (punkzip accelerator), G-128 (punkres stamper) - the two distributed tools whose recipes carry the same defect. - Related (archived): G-102 - the suite zig-version pin lives in the same comptime block as the defect. - Overlap survey 2026-08-06 (`goals_xref.tcl paths src/buildsuites/suite_tcl90 bin/punkzip.exe bin/punkres.exe`): surveyed and judged not related in substance - G-110 (shared-lib extraction cache), G-131 (boot payload autodetection), G-141, G-157, G-158 (bin/ sibling-file matches only). - Zig version gate (corrected 2026-08-06 - an earlier note here claimed the gate blocks rebuilding on this box; it does not): the recipes' SemVer gate does reject the stray prerelease copy under `bin/tools/zig-0.16.0-dev.254+6dd0270a1/` (a prerelease sorts below its release), but that copy is not the pinned toolchain. The `zigpin` record (`sources.config`) points at `bin/tools/zig-x86_64-windows-0.16.0/zig.exe`, a released 0.16.0 that is present, reports `0.16.0`, and passes the gate; `suite.tcl` and the README both resolve to it by default. The removed-zig claim arose from missing the `zig-x86_64-windows-*` naming family in `bin/tools/` and back-inferring a removal from the artifact records. Rejecting a 0.16.0-dev snapshot is arguably correct behavior besides - it predates the 0.16 API the recipes are written against - so nothing here blocks or needs fixing. - Verification note: `llvm-objdump -d --section=.text ` counting `zmm[0-9]` operands is the quick manual check used throughout the Context measurements. Linear disassembly of a non-AVX-512 binary yields ~3 false positives from data misparse; real usage is in the hundreds-to-thousands, so the distinction is unambiguous at a glance but the audit tool (Approach 4) should decode properly rather than grep. ## Progress - 2026-08-06: recipe pins applied (Approach 1+2) - `default_target` with `cpu_model = .baseline` in `src/buildsuites/suite_tcl90/build905.zig`, `src/buildsuites/suite_tcl86/build86.zig`, `src/tools/punkzip/build.zig` and `src/tools/punkres/build.zig`, each carrying the finding in-comment. Verified by `zig ast-check` on all four; the mechanism verified separately with the same toolchain and optimize level on an equivalent recipe (flagless -> baseline/zmm=0, `-Dcpu=native` -> znver5/zmm=805). A full suite build was NOT run in that session - recorded at the time as gate-blocked, corrected in the next entry (the pinned toolchain passes the gate). The `b.graph.host` target sites elsewhere in these recipes are build-time helper tools that are never distributed and are correct as native. - 2026-08-06 (correction, later session): the gate-blocker claim is wrong - the pinned toolchain `bin/tools/zig-x86_64-windows-0.16.0/zig.exe` (the `zigpin` record) is a released 0.16.0, present on this box and passing the recipes' gate; only the stray dev copy is rejected. Notes bullet rewritten accordingly. Nothing blocks the rebuild here. - 2026-08-06: user decision - republication is in-situ replacement under the existing artifact names/revisions (Approach 6 rewritten); punkbin immutability is deliberately violable during alpha with zero third-party consumers. Disposition of the r1-generation artifacts (replace vs drop) remains a pending curation call. - 2026-08-06 (later session): Approach 6 executed for the r2 generation + tools. Full `zig build bootstrap` with the pinned released 0.16.0 rebuilt the family at the baseline floor: all three r2 exes audit zmm=0 ymm=0 (was 2756/617), family_check self-contained PASS, punk-r2 build_id unchanged. punkzip 2.4.0 / punkres 0.3.1 rebuilt (zmm 1724/1423 -> 0), own suites pass; local bin/ copies replaced. Core test-gate: two consecutive PASS runs against the dispositioned baseline (69552 run / 8 failed, identical totals both runs; a first attempt failed once on interp-36.7, a bgerror timing race - passed on both reruns; one intermediate run hung 4h inside httpProxy.test "ThreadLevel 1", the socket-flake class - killed and rerun). lib tier audited clean (tcllibc.dll zmm=0; the critcl -target pin already protected it). Published in-situ: punkbin ab5611d (3 exes + 3 schema-2 tomls + 2 versioned tool exes + both sha1sums.txt), pushed to origin/master. Record-honesty residual: the published tomls' [tests] lines carry the emission-time (July) summaries; the fresh shipped-codegen gate evidence lives in _build testreports - fold a [tests] refresh into the Approach 3 record work. - Remaining for acceptance: Approach 3 (floor in the artifact record, folding in a [tests]-freshness refresh of the replaced tomls), 4 (audit tool + fixture), 5 (punk-runtime surfacing and the use/run verdict). Behind those: r1-generation disposition (replace vs drop) and the bin/ punk9* kit rebake with the new runtime (punkproject version-gate discipline applies). - 2026-08-07: goal activated at user direction (overlap survey re-run via `goals_xref.tcl score G-172` - no goals drafted in the interval; the existing Related: notes cover every overlapping pair). Approaches 3, 4, 5 CODE landed: - Approach 3 (record floor field): `src/buildsuites/suite_tcl90/build905.zig` declares `const cpu_floor = "baseline";` beside the default_target pin and writes `cpu_floor` + `cpu_model` (=`target.result.cpu.model.name`, resolving to "x86_64" for the default build, "znver5" for -Dcpu=native) into the embedded record's [provenance], after toolchain/optimize (ast-check PASS; 17 format placeholders = 17 args). `tools/family_artifacts.tcl` derives both from the embedded record (G-117 single-source-of-truth, like origin/packager) and emits them into the sidecar [provenance], failing if absent. `tools/family_check.tcl` asserts embed_cpu_floor/embed_cpu_model non-empty. suite_tcl86 has no family machinery (G-158 territory) - only the build86.zig cpu_model pin (already in place). NOT yet built/published - the floor field reaches published artifacts at the next republication. - Approach 4 (audit tool): `scriptlib/developer/cpufloor_audit.tcl` (plain tclsh, no deps) disassembles a binary's .text via a located objdump (llvm-objdump preferred, GNU objdump fallback) or audits a pre-disassembled excerpt (`-from-dis`), classifies by AVX register width (zmm=v4, ymm=v3; baseline/v2 forbid both, v3 forbids zmm, v4 allows all - SSE4.2 not width-detectable so baseline and v2 share a ceiling), reports out-of-floor count + first RVA + sample mnemonics, exit 0 PASS / 1 FAIL. Selftest (`-selftest`) runs 12 fixture/floor cases - all pass. Fixtures are REAL disassembly excerpts in `scriptlib/developer/cpufloor_fixtures/` (avx512, avx2, clean) generated from a temp-built probe on this Zen 5 host (no binary committed). Verified against real binaries: rebuilt bin/punkzip.exe PASS (zmm=0 ymm=0); a -Dcpu=native probe FAILs at baseline (123 zmm, 151 out-of-floor, first RVA 140002952) and PASSes at v4. The acceptance's measurable instrument is in place. - Approach 5 (punk-runtime surfacing): both payload twins updated and re-wrapped. `src/scriptapps/bin/punk-runtime.ps1` adds `Get-PunkHostCpuLevel` (IsProcessorFeaturePresent via Add-Type P/Invoke; PF SSE2=10/SSE4.2=38/AVX2=40/AVX512F=41 -> level 1..4, cached, 0 on detection failure), `Get-PunkCpuModelLevel` (x86_64/baseline->1, _v2->2, _v3->3, _v4->4, else 0), `Get-PunkCpuFloorVerdict` (meets|below|unknown| norecord; below => block). `punk-runtime.bash` mirrors with `host_cpu_level` (/proc/cpuinfo flags avx512f/avx2/sse4_2/sse2), `cpu_model_level`, `cpu_floor_verdict` (sets globals). Wired: fetch prints `cpu floor: floor=.. model=..` (UNGATED - cross-platform fetch stays free); list metadata_summary adds `floor=` and `!CPU-FLOOR:v` (local-platform rows only); use gates on the local platform (below => exit 1, named diagnosis); run gates before launch (below => exit 1); info adds cpu_floor/cpu_model to the embedded-vs-sidecar field table. `$host` is a read-only automatic variable in PowerShell - the PS verdict uses `$hlevel` (caught during testing). Re-wrapped via `punk::mix::commandset::scriptwrap::multishell punk-runtime -askme 0 -force 1` from src/scriptapps/bin; wrap is deterministic (re-wrap diff = identical); the scriptwrap_runtime_cmd_roundtrip_no_drift test PASSES. Host detection verified (PS level=4 on this Zen 5 box; bash /proc/cpuinfo level=4 in git-bash); verdict logic verified with a mocked v2 host (x86_64_v4 -> below/block, znver5 -> unknown, x86_64 -> meets) in both twins. Remaining for acceptance (publication-time, not code): rebuild the family + tools with the floor field in the record, republish in-situ to punkbin (per Approach 6's alpha dispensation), and run the audit tool against the republished artifacts to confirm zmm=0 (the acceptance's "flagless build emits zero instructions above floor"). The [tests]-freshness refresh of the replaced tomls folds into that republication. A real end-to-end `use`/`run` `below` block requires a host whose level is below a published artifact's recorded cpu_model - not demonstrable on this v4 host with baseline-floor artifacts (every host meets v1); the mechanism is verified by the mocked-host tests. Behind those: r1-generation disposition and the bin/ punk9* kit rebake. - 2026-08-07: APPROACH 6 executed (rebuild + republish) - the acceptance is met. Full `zig build bootstrap` (default steps: install, install-libraries, make-zipfs, smoke, tklib, tcllib, tcllibc, tcllibc-linux, kit-family, kit-family-artifacts, library-artifacts) with the pinned released 0.16.0 rebuilt the r2 family WITH the Approach 3 floor field now in the embedded [provenance] record (re-stage picked up the 2026-08-07 build905.zig edit; zig-cached C compiles, only the wrap + emit re-ran). family_check passed for all three members (the new embed_cpu_floor/embed_cpu_model assertions are green). Audit of the rebuilt r2 exes: all three PASS at the baseline floor with zmm=0 ymm=0 out-of-floor=0. Published in-situ to punkbin (git 59e7475, pushed to origin/master): the three r2 exes + three r2 tomls replaced under their existing names/revisions; each toml [provenance] now carries cpu_floor="baseline" + cpu_model="x86_64"; the [tests] tclcore line refreshed to the shipped-codegen gate evidence (passed=56041 failed=8, the Aug-06 baseline-rebuild test-gate - was 56039/9); library [tests] lines unchanged; sha1sums.txt: the 6 r2 lines (3 exes + 3 tomls) updated. build_id unchanged per artifact (the floor field does not enter the identity digest - intended); sha1/size/built updated (the embedded record text changed). The [tests] refresh folded in (the record-honesty residual from the prior republish is cleared). Verification of every acceptance conjunct (2026-08-07, this Zen 5 / v4 host): - flagless build emits zero instructions above the declared floor: the three republished r2 exes audit PASS (zmm=0) via scriptlib/developer/cpufloor_audit.tcl (also re-verified against the punkbin working-tree copies post-republish). - `-Dcpu=native` still yields a host-tuned local build, proving the floor is a default not a restriction: a temp probe built `-mcpu=baseline` -> zmm=0 PASS at baseline; `-mcpu=native` -> zmm=123 FAIL at baseline (out-of-floor=151, first RVA 140002952) and PASS at the v4 floor. (punkzip/punkres were already rebuilt+republished at zmm=0 in the prior session - the build-path tools are clean; suite_tcl86's lib tier (tcllibc.dll) was audited clean then too.) - each published artifact's metadata record states the floor: the republished tomls carry cpu_floor/cpu_model in [provenance] (verified by diff vs the prior ab5611d tomls and by punk-runtime info). - punk-runtime surfaces it: `info` shows the cpu_floor/cpu_model rows (embedded = sidecar, sha1 matches); `list` shows floor=baseline in the metadata summary; `use` selects cleanly on the meets verdict (this v4 host meets the baseline/v1 floor); `fetch` prints `cpu floor: floor=.. model=..` and stays ungated (code-verified; cross-platform fetch for another machine is not gated by a local-CPU verdict). - selecting/launching a runtime whose recorded floor the local CPU does not meet produces a named diagnosis instead of a silent 0xC000001D: verified by the mocked-host tests (2026-08-07, both .ps1 and .bash twins) - a recorded cpu_model=x86_64_v4 against a mocked v2 host yields the `below` verdict with the 0xC000001D message and use/run exit 1. NOT demonstrable end-to-end on this v4 host with a real published artifact: every published artifact is baseline (v1) which every host meets, and no x86-64 level exceeds v4 (the host's level), so no real below-floor artifact can exist here. The behavior is present and verified by the mocked-host tests; the limitation is the test host, not the implementation. - fetching for any platform stays ungated: code-verified (fetch prints the floor as information and never gates on the local CPU; the cross-platform -platform

workflow is a normal cross-machine path). ## Follow-ons Follow-on: r1-generation artifact disposition (replace vs drop) - the r1 exes remain AVX-512-only (2756 zmm each); under the in-situ alpha dispensation they could be replaced at baseline too, or dropped from defaults curation. No live goal home. => open Follow-on: bin/ punk9* kit rebake with the new floor-bearing runtime (punkproject version-gate discipline applies - the floor surfacing code shipped in 0.57.1; a rebake stamps it into kits). No live goal home. => open