12 KiB
G-125 A kit that cannot boot is not deployed
Status: achieved 2026-07-27 Scope: src/make.tcl (kit extraction outcome handling, deploy step gating, the no-extraction BUILD-WARNING as the current behaviour being replaced); src/modules/punkboot/utils-999999.0a1.0.tm (the boot-precondition predicate, called guardedly from make.tcl as vcs_dirty_warnings is); src/tests/modules/punkboot/utils/ (predicate characterization over directory fixtures); src/tests/shell/testsuites/punkexe/ (characterization of the make.tcl-side gate reporting); src/AGENTS.md (bake failure-mode documentation) Goal: a bake that could not lift a runtime's own payload (its tcl_library) into the kit fails that kit instead of building and deploying an artifact that cannot boot - so the worst outcome of a missing extraction capability is a red kit, never a silently replaced working executable. Acceptance: when extraction from the source runtime yields nothing and the kit's .vfs does not itself supply a bootable tcl library, the kit is recorded in FAILED KITS with a reason naming the cause and NEITHER src/_build nor bin receives a new artifact - the previously deployed kit is left untouched, verified by mtime and byte comparison across such a run; a kit whose .vfs legitimately supplies its own tcl library still builds and deploys, so the gate tests for the boot precondition rather than for the extraction step having run; the check is cheap enough to run on every kit and executes no artifact; the condition and its remedy are documented in src/AGENTS.md; existing punkexe suites pass unchanged and the gate itself is characterized.
Context
The kit machinery already knows when it failed to extract anything from a source runtime.
Under G-122 (achieved - see goals/archive/G-122-host-target-platform-split.md) that
notice was promoted from an inline stderr block to a recapped BUILD-WARNING: naming the
kit and what was tried - but the build still proceeds, assembles an artifact, DELETES the
deployed kit and copies the new one over it. The pre-deploy process sweep will even close
running instances of the kit first.
Field-observed 2026-07-26: an msys2-hosted bake of punk91 produced a 49,501,792-byte
bin/punk91.exe that failed at startup with
application-specific initialization failed: Cannot find a usable init.tcl
because the host's tclsh had no tcllib and therefore could not extract the runtime's own zip. The previously working kit had already been replaced by then. G-124 removes the particular cause, but not the class: a runtime with no attached payload at all, an extraction that fails for any other reason, or a future container type reaches the same state, and the build's response is still "warn and deploy".
The existing precedent in the same code path is vfs_startup_script_warning, which warns
about a missing main.tcl and continues - deliberately, because a kit without a startup
script is legal. A kit without a resolvable tcl library is not in the same category: it
cannot initialise at all.
Approach
- Gate on the BOOT PRECONDITION, not on the extraction step having run. Some .vfs folders
legitimately supply their own tcl library (the merged
targetvfsis what matters), so the check inspects the assembled tree rather than the extraction outcome - a runtime that needed no extraction must keep building. - Fail the kit the same way every other failure in that loop does (append to
failed_kitswith a reason, end the punkcheck event FAILED) BEFORE the artifact is renamed into place, so neithersrc/_build/<kit>norbin/<kit>is touched and the punkcheck records stay consistent with what is on disk. - Keep it cheap and non-executing: a structural check of the merged vfs, no artifact launch, so it can run for every kit on every bake.
- The predicate lives in
punkboot::utilsand make.tcl calls it through a guarded require, exactly as it already does forvcs_dirty_warnings- Scope extended at activation (2026-07-27, user-approved) because a predicate private to the build script cannot be characterized, and that module is the established home for make.tcl's gate helpers. - The recapped BUILD-WARNING wording changes from "will not initialise unless the vfs supplies one" to a failure reason, since the gate now enforces what the warning described.
Alternatives considered
- Keep warning and deploying, on the grounds that the developer sees the recap - rejected: the recap arrives after the deployed kit has already been replaced, and the failure surfaces later as an unexplained broken shell. A build tool should not make a working executable worse.
- Boot the freshly built artifact as the check - rejected: it cannot work for cross-target kits (G-122 makes those routine), costs a process launch per kit, and a kit that boots into an interactive shell is awkward to probe safely.
- Emit to
src/_buildbut refuse only the deploy - rejected as a half-measure that leaves a punkcheck record claiming a good build product; a later run would consider it current.
Notes
- Related: G-124 (achieved 2026-07-26 - see goals/archive/G-124-punkzip-reader.md) - removed
the most common cause: a tcllib-less tclsh can now read a runtime's attached zip, and a
bake from msys2's tclsh8.6 carries
tcl_libraryinto the kit instead of warning past it. This gate is independent of that and remains valuable: the class survives (a runtime with no attached payload, an extraction that fails for any other reason, a future container type), and the build's response to it is still "warn and deploy". Note for the gate's design: the reader now RAISES with a named reason (encrypted, zip64, unknown compression method, crc mismatch, offsets that do not describe the file) where the old path returned an opaque tcllib error, so the gate has a usable failure reason to report. - Related: G-122 (achieved) - promoted the no-extraction notice to a recapped BUILD-WARNING and recorded the field incident that motivates this goal; see goals/archive/G-122-host-target-platform-split.md.
- Related: G-127 (recorded at activation 2026-07-26 - drafted after this goal, and the activation-freshness survey's top unlinked pair on shared paths: src/make.tcl deploy step, src/tests/shell/testsuites/punkexe/, src/AGENTS.md). It moves non-native kits to bin/kits// and adds its own payload/target mismatch reporting, so this gate must refuse before whichever output location that goal settles on, and the two failure reports should read as one vocabulary rather than two.
- Related: G-101 - a new 8.6 container type is exactly the situation where an extraction path can silently produce nothing; the gate is what keeps that honest.
- Related: G-028 - sibling failure-reporting surface in the same deploy step (that goal names the process holding a target the build cannot replace; this one refuses to replace a target with something unbootable).
- Overlap survey also considered G-057 (icon embedding rides the same wrap steps) and G-115 (declarative .vfs composition decides what the vfs supplies) - neither is a dependency: this gate reads whatever the merged tree contains, however it was composed.
Progress
2026-07-27 - implemented; every Acceptance clause verified. Detail:
punkboot::utils0.2.0 -> 0.3.0 gainsvfs_boot_library_report {vfsfolder}, returningok/locations/rejected/checked/reason. make.tcl calls it through::punkboot::get_vfs_boot_library_report, a guarded require modelled on the existingget_src_provenance_warnings, so a stale bootsupport snapshot degrades the gate to a NOTE rather than failing every kit. Both branches were observed live: before the bootsupport propagationmake.tcl checkreported UNAVAILABLE, after it ACTIVE.- The gate sits in the kit loop immediately after the
_vfscommon.vfs+<kit>.vfsmerges and before the kit-type assembly switch - the last point at which no build product exists. A refusal appends tofailed_kits, ends the punkcheck event FAILED andcontinues, sosrc/_build/<kit>andbin/<kit>are never touched and the punkcheck records stay consistent with what is on disk. Note the mergedsrc/_build/<kit>.vfstree IS left behind (it is the evidence for the refusal); only the artifact is refused. - What qualifies as a tcl library:
tcl_library/init.tcl(zipfs-attached kits) orlib/tcl<major>.<minor>/init.tcl(starkit-style), AND at least one companion file (tm.tcl,package.tcl,auto.tcl,clock.tcl,history.tcl,word.tcl) or anencoding/directory beside it. The companion test is load-bearing: every punkshell kit carrieslib/BWidget1.10.1/init.tclandlib/BWidget1.9.16/init.tcl, so a check that looked for anyinit.tclwould pass a kit with no tcl library at all.lib/tcllib2.0is correctly not matched by thelib/tcl[0-9]*glob. - The no-extraction BUILD-WARNING was reworded from "will not initialise unless src/vfs/ supplies one" (which described a consequence the build then ignored) to "will FAIL the boot-precondition gate unless src/vfs/ supplies one". It stays a warning rather than becoming the failure, because a .vfs that supplies its own library needs no extraction - the gate decides, on the merged tree.
Verification records (2026-07-27)
- Predicate over the 10 assembled kit trees in
src/_build: all pass, and the two conventions are both represented (tcl_library for punk902z/punk905/punk91/punk9_beta/ punk9bi_beta/punkmagic, lib/tcl8.6 for punk86/punkbi/punksys, lib/tcl9.0 for punkshell902). Of the source.vfsfolders three legitimately supply their own library (mkzipfix.vfs, punk8_statictwapi.vfs, punk9test.vfs) - the case the gate must not break. - Gate fired end-to-end against a temporary fixture kit (a mapvfs entry pointing a zip kit
at a payload-free runtime plus a vfs with no tcl library), with sentinel files standing
in for a previously deployed kit and a previous build product:
bin/punkgatefixture.exesha1 2a0700ebfad097e34f70f6d779c8c1c8f2ba7dba andsrc/_build/punkgatefixture.exesha1 12c505679ae7cc8fe44527eb4e8f34fa2659894d were byte-identical with unchanged mtimes across the failing run, and the kit was reported under FAILED KITS with the cause named. Addingtcl_library/{init.tcl,tm.tcl}to that same fixture vfs - extraction still yielding nothing - made the kit build and deploy normally (INSTALLED KITS: 1), which is the "tests the precondition, not the extraction step" half of the criterion. The fixture (mapvfs entry, vfs folder, fake runtime, sentinels, build leftovers) was removed afterwards and mapvfs.config restored byte-identical to its backup. - Real-kit bake through the gate:
bake -confirm 0 punk905builds, deploys and boots (tcl_library at //zipfs:/app/tcl_library, punkboot::utils 0.3.0 present in the kit). src/tests/modules/punkboot/utils/testsuites/utils/bootlibrary.test12/12;src/tests/shell/testsuites/punkexe/maketclbootgate.test2/2.make.tcl workflowrenders the gate in the kit data flow with key note K10, verified under the default and PUNKBOOT_PLAIN=1 paths, max line width within the 100 budget.- Cost: 0.63ms per kit measured over 200 iterations against the real
src/_build/punk905.exe.vfstree (aglobplus a handful offile isfiletests, no recursion, nothing executed) - negligible beside a bake that takes minutes, which is what lets it run for every kit unconditionally. - Full source-tree suite after the change: 1161 tests, 1138 passed, 20 skipped, 3 failed.
One is
core/tcl exec-14.3, the documented pre-existing baseline. The other two are inmodules/argparsingtest/and belong to unrelated work in progress in the working tree at the time of the run (that module, its buildversion and both of its test files were being edited concurrently); nothing in this change reaches arg-parsing benchmarks.