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.
 
 
 
 
 
 

16 KiB

G-129 Kit boot finds its payload wherever the archive mounted

Status: achieved 2026-07-27 Scope: src/vfs/_config/punk_main.tcl and src/vfs/_config/project_main.tcl (vfs capability detection plus mount-point derivation for the kit's internal module/lib path assembly - both carry the same code today); src/runtime/mapvfs.config (an active kit entry per verification runtime); bin/runtime/win32-ix86/ (the two third-party 32-bit runtimes that verify it - read-only input); src/tests/shell/testsuites/punkexe/ (characterization of the derivation); src/AGENTS.md + bin/AGENTS.md (what a runtime must provide to be kit-wrappable) Goal: a punk kit boots from wherever its attached archive actually mounted, so a runtime whose zipfs mounts at the executable's own path rather than at //zipfs:/app - as the androwish/undroidwish 8.6 backport does - yields a kit that finds its own modules and libs. The boot derives the mount point from the runtime's own mount list and detects zipfs by a command every generation provides, instead of keying on tcl::zipfs::root (a 9-era command absent from the 8.6 backport) and a compiled-in //zipfs:/app. Acceptance: a kit baked from a runtime that mounts its attached archive at the executable's own path boots and resolves its own payload with no external package paths in play - the kit's internal modules/ and lib/ trees appear in tcl::list and auto_path and a package that exists only inside the kit resolves - with bin/runtime/win32-ix86/tclsh8.6.10-luck-zip.exe recorded here as that verification runtime; a kit baked from bin/runtime/win32-ix86/tclsh9.1b0-tclsfe.exe (32-bit, modern //zipfs:/app convention) boots the same way, proving the 32-bit target itself needs nothing special and that the derivation did not regress the ordinary case; punkshell's existing 64-bit kits are unaffected, verified by comparing each kit's internal tcl::list and auto_path entries before and after the change across at least one zipfs kit and one starkit-family kit (identical); a runtime that has an attached archive the boot cannot locate says so on stderr rather than continuing silently with no internal paths, which is today's failure shape; and the runtime requirements for kit-wrapping - which zipfs commands and mount conventions are supported - are documented in bin/AGENTS.md.

Context

src/vfs/_config/punk_main.tcl decides whether the running kit has an attached zipfs image, and where it is, with two assumptions that were true for every runtime punkshell had until now:

set has_zipfs [expr {[info commands tcl::zipfs::root] ne ""}]
...
set zipbase [file join [tcl::zipfs::root] app]
if {"$zipbase" in [tcl::zipfs::mount]} { ...add kit modules/ lib/ paths... }

tcl::zipfs::root arrived with the 8.7/9 zipfs. Christian Werner's androwish / undroidwish backport gives Tcl 8.6 a working zipfs - mount unmount info list exists mkzip mkimg mkkey lmkzip lmkimg - but no root, and it mounts the executable's attached archive at the EXECUTABLE'S OWN PATH rather than at //zipfs:/app, so [info library] reads <exe>/tcl8.6. Against such a runtime punkshell concludes "no zipfs", the //zipfs:/app branch never runs, ::tcl::kitpath is unset so the starkit branch does not run either, and the kit silently gets no internal module or lib paths at all. Nothing is reported; the failure surfaces later as packages that cannot be found.

Measured 2026-07-27 on two third-party 32-bit windows runtimes now in the store, which bracket the problem:

tclsh8.6.10-luck-zip.exe tclsh9.1b0-tclsfe.exe
provenance Lean Undroidwish Construction Kit apnadkarni/tcl-sfe releases
Tcl 8.6.10 9.1b0
tcl::zipfs::root absent //zipfs:/
::zipfs ensemble present, but no root subcommand (corrected 2026-07-27) present
mount point <exe> //zipfs:/app
[info library] <exe>/tcl8.6 //zipfs:/app/tcl_library
zipfs in child/thread interps none present
attached archive archive-relative, 1757 members archive-relative, 892 members
vfs::zip / starkit 1.0.4 / 1.3.3 absent

The LUCK runtime already drives make.tcl shell fine - in that mode punkshell loads from the source tree, not from a kit payload - which is what made the gap easy to miss.

Approach

  • Detect zipfs by a command the backport also provides (tcl::zipfs::mount is the natural one, and is what the mount list comes from anyway) rather than by tcl::zipfs::root.
  • Derive the mount point from tcl::zipfs::mount itself. The mount list pairs mountpoint with the file mounted there, so the entry whose file is [info nameofexecutable] names this kit's own archive wherever it landed - //zipfs:/app on a modern runtime, the exe path on the backport. Prefer that over any compiled-in constant; the existing //zipfs:/app remains the expected answer, not a hardcoded requirement.
  • Keep ::tcl::kitpath (starkit/tclkit) and the cookfs branch exactly as they are - this is about the zipfs branch only.
  • When an archive is attached but no mount entry can be matched to this executable, say so on stderr. Silence is the current failure shape and it is what made this expensive to diagnose.

Alternatives considered

  • Special-case the androwish backport by name or by Tcl version - rejected: the mount list already carries the ground truth, and a version test would need revisiting for every runtime family that does something reasonable but different.
  • Require runtimes to mount at //zipfs:/app and declare the backport unsupported - rejected: the mount point is chosen by the runtime's own boot code (TclZipfs_AppHook), not by the image we assemble, so this is not something a bake could enforce; and 8.6 is a supported target (src/AGENTS.md) where this backport is one of the few working zipfs options.
  • Have the bake stamp the expected mount point into the kit's main.tcl - rejected: it would bake a build-host assumption into the artifact, and the running kit can simply look.

Notes

  • THIS GOAL IS THE LAST BLOCKER for baking a working kit on a third-party runtime, which is why it stands alone rather than waiting on any larger container work. The chain is: read the runtime's attached payload (G-124, achieved), do not let the boot-precondition gate falsely refuse it (G-125, achieved - plus the punkboot::utils 0.4.0 third-convention fix), and then have the baked kit FIND that payload at boot - this goal. The first two are done; without the third a kit baked on such a runtime builds, deploys and starts, and then resolves none of its own modules. Measured 2026-07-27 by running punk_main.tcl's detection logic verbatim under bin/runtime/win32-ix86/tclsh8.6.10-luck-zip.exe:

    has_zipfs (punk_main.tcl:290 verbatim): 0
    zipbase: NEVER COMPUTED - the //zipfs:/app branch is skipped entirely
    kitpath branch: SKIPPED - kitpath unset
    => internal modules/ + lib/ paths added: NONE
    (while tcl::zipfs::mount really does report the archive mounted at the exe path)
    

    So the failure is not "mounted at an unexpected place and therefore mis-resolved" - it is "concluded there is no zipfs at all, and skipped every internal-path branch".

  • DOWNSTREAM: G-131 (back-pointer added 2026-07-27) generalises this goal's derivation from "where is the zipfs image mounted" to "which container is attached at all - metakit or zipfs". That does not change what this goal must deliver, but it does say something about HOW: keep the derivation factored as a step that answers a question about the running kit, rather than inlining a zipfs assumption into the surrounding path-assembly code, so the metakit arm can be added beside it rather than around it. G-131 depends on this goal and on G-101's port work; this goal depends on neither and ships on its own.

  • Related: G-031 - the closest structural overlap. That goal restructures this same file into a thin project-owned main plus a shared boot core; this changes logic that would live inside that core. Whichever lands second adopts the other's shape - the derivation belongs in the boot core, not in a per-project main.

  • Related: G-101 - its candidate container list already includes "zipvfs-over-tclvfs boot". A working 8.6 zipfs backport that mounts at the exe path is direct evidence for that assessment, and the LUCK runtime is a ready-made specimen: it carries zipfs, vfs 1.4.2 and starkit 1.3.3 together.

  • Related: G-123 - these two runtimes are third-party punkbin candidates, and whether a runtime is kit-wrappable is what decides if it publishes as a usable runtime tier rather than only as a curiosity. The bin/AGENTS.md requirements this goal writes are the criterion that publication would cite.

  • Related: G-125 (achieved 2026-07-27 - see goals/archive/G-125-unbootable-kit-deploy-gate.md)

    • the bake's boot-precondition gate learned the LUCK runtime's tcl<M>.<m>/ library convention (punkboot::utils 0.4.0) so it no longer refuses such a kit as unbootable. That fix only made the kit BUILDABLE; making it BOOT is this goal.
  • Deliberate non-overlap: G-114 (per-platform tm module roots) and G-127 (cross-target bake output locations) touch punk_main.tcl and the kit surfaces too, but on the tm-roots and output-location axes - neither reads or writes the vfs-detection logic.

  • src/vfs/_config/project_main.tcl carries the identical probe and mount derivation, so the same defect ships into every generated project. Both files are in Scope; if G-031 lands first this collapses to one place.

  • Both verification runtimes are untracked (bin/runtime/ is not in VCS, per G-004), so any test that names them self-gates on their presence - the same pattern zipreader_piperepl_runtime uses for tclsh90b4_piperepl.exe in src/tests/modules/punk/zip/testsuites/zip/zipreader.test. The LUCK build is reproducible from the service: bin/runtime/win32-ix86/tclsh8.6.10-luck-zip.cfg is the base64 build request (output name, the 38 selected packages, apptext, cmdline) and LUCK.url names the generator.

  • Related: G-130 (back-pointer recorded at activation 2026-07-27, from the activation overlap survey) - builds punkshell's own 32-bit runtimes from the buildsuites; the two third-party win32-ix86 runtimes this goal verifies against are its comparison specimens, and the "32-bit dimension" note below describes the interest that goal carries.

  • The 32-bit dimension is wider than these two runtimes: the developer's standing interest (recorded 2026-07-27) is 32-bit zig builds for punk, because old firewalled or sandboxed systems - industrial ones especially - may carry tcl8-only or tcl9-only binary modules that have to be dropped in, and no single source ships current Tcl for both major versions. That is separate work; this goal only ensures a 32-bit third-party runtime can carry a kit, and leaves two verification specimens in the win32-ix86 tier for it.

Progress

2026-07-27 - implemented and verified in one pass; every Acceptance clause met (implementation commit 191d8ff4, punkshell 0.28.0). Detail:

  • Boot derivation (punk_main.tcl + project_main.tcl, identical copies): zipfs presence keys on tcl::zipfs::mount; the mount base comes from the new factored ::punkboot::zipfs_kit_mountbase (mount-table entry whose archive file is [info nameofexecutable], accepting the backport's drive-prefix-stripped spelling; fallback = the mount containing [info script]; helpers zipfs_mount_pairs + path_within; all remain defined post-boot for probing). The starkit quick-search line that called [zipfs root]/app/tcl_library (an ERROR on the backport - no root subcommand anywhere) uses the derived base; internal_paths gains the derived base when no zipfs volume root covers it; the internal-only simulate-kit discriminator recognises a script under the derived mount as kit-internal. starkit/cookfs branches untouched, per Approach.
  • Measured mount tables: both generations return flat {mountpoint archivefile} pairs. LUCK 8.6.10 backport: mountpoint = the exe path itself, archive-file column WITHOUT the windows drive prefix (/repo/... for C:/repo/...). tcl-sfe 9.1b0/9.0.x: //zipfs:/app + full exe path.
  • Boot-script hook (the second blocker, found during verification): the LUCK/undroidwish family only auto-runs app/main.tcl INSIDE the attached archive (embedded /app/main.tcl string in the exe) and ignores a root main.tcl - without it the kit fell through to stock tclsh argv handling and main.tcl never ran. punk8win.vfs now carries an app/main.tcl fauxlink beside the root one. Enabling that nested fauxlink required make.tcl's merge_over to resolve fauxlink targets against the link file's own directory (the fauxlink module's documented symlink semantics; identical for the root-level links that were previously the only ones).
  • Scope extension (flagged for user ratification, G-125 precedent): src/make.tcl was necessarily touched - the runtime capability probe keyed has_zipfs on tcl::zipfs::root (same defect class on the build side: it refused zip assembly for the backport with "runtime ... doesn't have zipfs capability"; the block also rode a body-less if {![catch ..]} that only worked because piped-stdin tclsh continues past command errors) and the merge_over fauxlink base fix above. Also outside drafted Scope: src/vfs/punk8win.vfs/app/ (the new fauxlink), src/vfs/AGENTS.md, src/tests/shell/AGENTS.md, ARCHITECTURE.md (docs), and the layout/templates-modpod synced copies of the boot mains + make.tcl.
  • Kit names (agent-chosen, open to rename): punkluck86 / punk91ix86.

Verification records (2026-07-27)

  • bin/punkluck86.exe (LUCK runtime + punk8win.vfs, zip concat assembly): boots from the exe-path mount; tcl::list carries /modules; auto_path carries /lib + /lib_tcl8 plus the runtime's own archive-internal trees (tcl8.6, tcl8/8.x - correctly classified internal now); punk::args 0.12.7 resolves from /modules/punk/args-0.12.7.tm; TCLLIBPATH-derived external paths stripped (default internal mode); the script subcommand runs app-punkscript from the payload. Remaining noise is expected 32-bit/8.6.10 degradations (no usable twapi -> is_interactive warning, tsv unavailable notes), not boot-path issues.
  • bin/punk91ix86.exe (tcl-sfe 9.1b0 32-bit + punk9win_for_tkruntime.vfs): modern //zipfs:/app trees, punk::args from inside the mount, clean stderr - the 32-bit target itself needed nothing special.
  • 64-bit kits unaffected: punk905 + punk902z (zipfs) and punksys + punk86 (starkit family) rebaked with the new boot; full probe output (tcl::list + auto_path + stderr) byte-identical to pre-change captures of the deployed kits. punk86 also carries the new inert app/main.tcl payload file - no path or behaviour change.
  • Attached-but-unattributable stderr report: tclkit902 (zipfs commands, empty mount table) with a freshly mkzip'd foreign archive mounted, then sourcing the working-tree punk_main.tcl - reports the archive and full mount table on stderr, continues booting on the metakit branch, and the foreign mount contributes no kit paths.
  • Both zip offset conventions boot on the backport: the deployed concat kit AND a zipfs-mkimg (file-relative offsets) image of the same tree both mount and resolve - recorded in bin/AGENTS.md so future zipfs-capable-host bakes are known-safe.
  • Characterization: src/tests/shell/testsuites/punkexe/kitmountpoint.test (4/4; self-gates on the untracked runtimes); punkexe subtree 69 tests, 0 fail; full suite 1171 tests with only the documented exec-14.3 baseline failure.
  • Runtime requirements documented: bin/AGENTS.md "Kit-wrappable runtime requirements (G-129)" + src/AGENTS.md pointer, src/vfs/AGENTS.md boot contract, ARCHITECTURE.md boot-chain bullet, src/tests/shell/AGENTS.md suite doc.