You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

13 KiB

G-133 Bakes verify payload/target consistency

Status: achieved 2026-07-27 Scope: src/make.tcl (post-merge advisory arch scan at the G-125 gate seam; post-bake smoke-require step for host-runnable kits; recapped reporting); src/modules/punkboot/utils-999999.0a1.0.tm (binary-arch classifier + payload scan predicate beside vfs_boot_library_report); src/runtime/mapvfs.config (per-kit smoke-require declaration - interim line format, schema home moves with G-024); src/tests/modules/punkboot/utils/ (classifier fixtures); src/tests/shell/testsuites/punkexe/ (reporting characterization); src/AGENTS.md + bin/AGENTS.md (what is and is not guaranteed) Goal: a bake tells the developer, before anyone runs the kit, when a kit's payload cannot deliver what it visibly carries: every kit including cross-target gets a structural check that binary libraries match the kit's declared target unless they sit under a platform-discriminated subdir, and host-runnable kits prove a declared package set actually resolves and loads inside the built artifact via plain package require - so resolution-order defects (a higher-versioned wrong-arch copy shadowing a working one) are caught, not just missing files. Acceptance: a merged payload carrying a wrong-arch binary outside any platform-discriminated subdir produces a recapped BUILD-WARNING naming file, found architecture and kit target, with the 2026-07-27 punkluck86 case (x64 thread dll under plain lib_tcl8/ in a win32-ix86 kit) reproduced as a fixture; multi-arch platform-subdir packages (iocp-2.0.2's win32-ix86 + win32-x86_64 pair) and recognised vendor spellings (blend2d-style win-x64) produce no warning; the scan is structural, non-executing, runs for cross-target kits and its measured per-kit cost is recorded as negligible; a kit entry may declare smoke-require packages, and for host-runnable kits the freshly built artifact is executed via its tclsh subcommand with each declared package plain-required - failures are recapped naming kit, package and the actual error, the Thread version-shadowing case is caught by construction, cross-target kits skip with a stated reason, and undeclared kits run nothing new; the classifier is unit-tested over PE i386/x64/arm64 fixtures and platform-subdir layouts with non-PE formats classified or honestly unknown; and src/AGENTS.md + bin/AGENTS.md state what the checks do not guarantee (statics, pure-tcl packages with binary deps, and version-preference outcomes are visible only to the smoke probe).

Context

Motivating failures, both measured 2026-07-27 on the G-129 verification kits when they still wrapped full 64-bit payloads (user-observed first as "basic shell functionality is broken"):

  • punkluck86 (32-bit LUCK 8.6.10 runtime): the payload's x64 Thread 2.8.12 outversioned the runtime's own working 32-bit 2.8.5, so plain package require Thread selected the unloadable copy and the repl's tsv/codethread machinery died. Forcing package require -exact Thread 2.8.5 loaded fine - the backport's zipfs dll loading works, and the failure was purely wrong-arch version shadowing. No structural check can see this class: the working file exists, and only actual resolution order exposes it.
  • punk91ix86 (32-bit tcl-sfe 9.1b0): package require udp failed against an x64-only tcludp dll. Both failures surfaced as the same unhelpful "couldn't load from current filesystem".

The G-125 boot-precondition gate deliberately checks BOOTABILITY (a tcl library in the merged tree) and nothing more; nothing between it and the user exercises whether the payload's packages can actually load in the built artifact. The minimal-payload repoint (punk8min.vfs/punk9min.vfs, 0.28.1) removed today's instances but not the class: any future payload/target pairing can regress the same way, silently.

Approach

  • TWO mechanisms because each sees what the other cannot. The smoke probe uses plain package require inside the real artifact, so it observes actual resolution order - the only way to catch version-preference shadowing - but requires a host-runnable kit. The structural scan cannot see resolution or statics, but it is non-executing and so covers cross-target kits (the linux entry included).
  • Arch scan: classify each binary library in the merged tree by header (PE Machine field
    • a two-byte read at e_lfanew+4: i386/x64/arm64; ELF/Mach-O classified or honestly unknown) against the kit's declared target. Binaries under a platform-discriminated subdir are exempt: canonical punkshell platform-dir names plus a small recognised vendor-spelling list (the tree already carries iocp-2.0.2's win32-ix86/win32-x86_64 pair and blend2d's win-x64). Advisory BUILD-WARNING, never a gate - multi-arch payloads are legitimate and vendor spellings make false certainty unavoidable.
  • Smoke step: per-kit declaration; the freshly assembled artifact is executed via its own tclsh subcommand (exits cleanly even on runtimes whose full repl teardown is fragile - the punkluck86 exit-segfault observation). Drafted ADVISORY (recapped failures); whether it should FAIL the kit like the G-125 gate is an open decision at activation.
  • The classifier/scan predicate lives in punkboot::utils beside vfs_boot_library_report, called through the same guarded require (G-125 precedent) so it is unit-testable without a bake and a stale bootsupport snapshot degrades it to a note.

Notes

  • Related: G-024 - the toml conversion is the eventual home for per-kit/group smoke-require declarations and the verification-set grouping recorded there; this goal lands an interim declaration in the current line format.
  • Related: G-131 - its kit_type_mismatch rework reads the same runtime_caps/probe surface; the two failure reports should read as one vocabulary.
  • Related: G-127 - the smoke step's host-runnable gating parallels that goal's cross-target output-location concerns; whichever lands second adopts the other's target-addressing.
  • Related: G-028 - sibling failure-reporting surface in the same bake loop.
  • Related: G-130 - first-party 32-bit runtimes/packages will change what the win32-ix86 verification kits may carry; the scan's platform-subdir exemption is what lets multi-arch payloads grow without warnings.
  • Related: G-123 - a published third-party runtime tier consumes this guarantee (a kit that passed its smoke-requires is what publication would cite).
  • Deliberate non-overlap: G-115 - declarative .vfs composition decides what a payload contains; this goal reads whatever the merged tree contains, however composed (the same stance archived G-125 took).
  • Precedent/motivation: G-125 (achieved - see goals/archive/G-125-unbootable-kit-deploy-gate.md) gates bootability at the same seam; G-129 (achieved - see goals/archive/G-129-kitboot-mountpoint-derivation.md) produced the two win32-ix86 verification kits whose payload/arch failures motivated this goal.
  • Related (activation-freshness survey 2026-07-27): G-114 - prevention-side sibling: per-platform tm roots would stop wrong-platform binary modules from ever being registered at runtime; this goal detects the same class at bake time for whatever layout a payload actually carries. G-128 - sibling PE-format surface (resource stamping of finished kit executables); both read PE structures but share no code path today. G-126 also surfaced (shared bake/zip vocabulary) - incidental machinery overlap only, no relationship recorded.
  • Open decision resolved at activation (2026-07-27): smoke-require failures are ADVISORY as drafted (recapped BUILD-WARNINGs; the kit still builds and deploys) - the user activated without electing the kit-failing-gate alternative, and the drafted Acceptance is written for the advisory behaviour. Revisit as a gate if field use shows recapped warnings being missed.

Progress

2026-07-27 - implemented and achieved same day; every Acceptance clause verified. Detail:

  • punkboot::utils 0.4.0 -> 0.5.0 gains binary_arch_classify (PE Machine field at e_lfanew+4; ELF e_machine honouring EI_DATA endianness; Mach-O thin + fat/universal; unmapped machine values report as definite unknown-0x<hex>, unrecognisable content as honest unknown), platform_expected_binary, platform_discriminated_segment (canonical <os>-<cpu> token lists + vendor-spelling list, all plain namespace variables for visible extension) and vfs_binary_arch_report (iterative walk, exemption decided per directory so exempt subtrees are counted without being read).
  • make.tcl: ::punkboot::get_vfs_binary_arch_report guarded require (G-125 pattern, degrades to a NOTE); scan wired immediately after the _vfscommon.vfs + <kit>.vfs merges, before the G-125 gate, so even a gate-refused kit reports its arch findings; per-file BUILD-WARNINGs capped at 8 with an honest total line. Smoke step after the artifact rename: ::punkboot::kit_smoke_require_probe writes a generated probe script beside the artifact and runs <artifact> tclsh <script> with drained stdin via punk::lib::invoke; per-package OK lines to stdout, failures + missing results + nonzero exit as recapped BUILD-WARNINGs. mapvfs.config entries accept a 5th element (mapvfs_parse/mapvfs_kit_outputs carry smokerequire; bakelist rows unchanged). check reports scan ACTIVE/UNAVAILABLE + the declared smoke matrix; workflow text gains key note K11.
  • Declarations: punkluck86 {Thread} (the incident construction), punk91ix86 {Thread iocp} (static builtin + platform-subdir dll selection), punkshell902 {Thread} (cross-target skip demonstrator; a linux-hosted bake would probe it).

Verification records (2026-07-27)

  • Live bake (bake -confirm 0 punkluck86 punk91ix86 punk86 punkshell902, all 4 INSTALLED): punkluck86 scan OK (26 scanned, 8 exempt) and smoke Thread -> 2.8.5 (the runtime's working 32-bit copy - plain require inside the artifact IS the shadowing construction); punk91ix86 smoke Thread -> 3.0.5 (static) and iocp -> 2.0.2 (win32-ix86 subdir dll selected in the 32-bit process); punkshell902 (cross-target linux-x86_64) scanned structurally (6 ELF libraries matched) and smoke skipped with the stated not-executable-here reason; punk86 produced the recapped BUILD-WARNING naming file, found architecture and kit target.
  • REAL FINDING on the scan's first sweep: zint.dll 2.13.0 in src/vfs/punk8win.vfs/lib_tcl8/ is PE i386 (independently confirmed with file(1)), carried by all three x64 tcl8 kits (punk86/punkbi/punksys) where its unconditional pkgIndex load can never succeed. Left in place - payload curation is the user's; those bakes warn until it is fixed. The same 32-bit dll also sits in src/vfs/punk8_statictwapi.vfs/lib/ (not currently baked). Zero false positives: across all 12 assembled trees (494 binaries scanned) the only mismatches are the three zint instances.
  • Negative smoke fixture: zint temporarily declared on punk86 and the kit force-rebuilt - recap produced smoke-require FAILED for kit punk86.exe: package require zint -> couldn't load library ... Bad exe format. Possibly a 32/64-bit mismatch. (kit, package, actual error - both mechanisms attributing the same real defect at bake time). mapvfs.config restored byte-identical from backup afterwards.
  • Cost: warm-cache scan of all 12 assembled trees 29-139ms per kit, avg 92.6ms, 1.11s total - negligible beside a bake that takes minutes, which is what lets it run unconditionally including for cross-target kits.
  • modules/punkboot/utils/testsuites/utils/binaryarch.test 23/23 (PE i386/x64/arm64 + unmapped-machine + MZ-no-PE fixtures, ELF LE/BE, Mach-O thin/universal, java-class cafebabe decoy, text/short honest unknowns, the punkluck86 case as a fixture, iocp pair + win-x64 exemptions, subtree exemption, format mismatch, unmapped target, no-write check, real-trees sweep with the zint findings pinned as known-real and filtered so fixing the payload keeps it green). All fixtures generated with binary format - no committed binaries.
  • shell/testsuites/punkexe/maketclpayloadcheck.test 3/3 (check status/contract/ declared-matrix, ESC-free per G-113); sibling make-family punkexe suites (bootgate/bakelist/colour/platform) 14/14 unchanged.
  • Both guarded-require branches observed live: check reported UNAVAILABLE before the bootsupport propagation, ACTIVE after (make.tcl modules + bootsupport + vfscommonupdate -confirm 0; utils-0.5.0 promoted, 0.4.0 pruned).
  • Full source-tree suite: 1197 tests, 1176 passed, 20 skipped, 1 failed - the documented pre-existing core/tcl exec-14.3 baseline only.
  • make.tcl workflow renders the checks as key note K11 in both default and PUNKBOOT_PLAIN=1 modes, max line width within the 100 budget.
  • Remaining manual item (user decision): resolve the zint payload defect (obtain an x64 zint build, move it under a platform-discriminated subdir with index logic, or drop it from punk8win.vfs); punk905/punk91/punk902z/punk9_beta/punk9bi_beta/punkbi/ punksys/punkmagic were deliberately not rebaked this session (avoids the pre-deploy sweep closing running user shells) - the next make.tcl bake/bakehouse picks up the refreshed _vfscommon.vfs (utils 0.5.0) for them.