Browse Source

G-134 achieved: flip, archive record - archive-relative output is a checked contract; punkshell 0.30.7

Implemented and achieved same day. punkboot::utils 0.6.0 gains
kit_offsetstyle_report (argdoc'd thin classifier over
punk::zip::archive_info), synced via modules + bootsupport (one stale
dead-pid .punkcheck.lock removed per the recorded recovery) and promoted into
_vfscommon. make.tcl: guarded getter, post-assembly probe beside the
smoke-require block (recapped BUILD-WARNING only on a FILE-relative attached
payload; plain/none/unreadable silent; NOTE on stale bootsupport), and the
'check' row (ACTIVE (advisory) + contract lines). Characterization:
offsetstyle.test 7/7 (mkzip -runtime/-offsettype hermetic fixtures for
plain/archive/file, none on text + metakit-magic binary, unreadable detail,
src/_build baseline sweep - no kit image probes file);
maketcl_check_offsetstyle_pin added (payload-check suite 4/4). Full utils
suite 69/69, punkexe suite 81/0, live punkluck86 bake silent as the
acceptance's baseline clause requires. Docs: src/AGENTS.md pin bullet with
does/does-not-guarantee, bin/AGENTS.md deployed-kit section, ARCHITECTURE.md
check-family bullet (architecture_lint clean). Reference sweep: no live-tier
references beyond the index entry; goals_xref shows no pairs bridged solely
by G-134. Archive move follows as a pure-rename commit.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 1 day ago
parent
commit
4aae330b31
  1. 2
      ARCHITECTURE.md
  2. 14
      CHANGELOG.md
  3. 4
      GOALS-archive.md
  4. 4
      GOALS.md
  5. 8
      bin/AGENTS.md
  6. 33
      goals/G-134-baked-kits-pinned-archive-relative.md
  7. 2
      punkproject.toml
  8. 1
      src/AGENTS.md
  9. BIN
      src/bootsupport/modules/punk/mix/templates-0.2.0.tm
  10. 67
      src/bootsupport/modules/punkboot/utils-0.6.0.tm
  11. 43
      src/make.tcl
  12. 560
      src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl
  13. 61
      src/modules/punkboot/utils-999999.0a1.0.tm
  14. 3
      src/modules/punkboot/utils-buildversion.txt
  15. 560
      src/project_layouts/vendor/punk/basic/src/make.tcl
  16. 560
      src/project_layouts/vendor/punk/project-0.1/src/make.tcl
  17. 119
      src/tests/modules/punkboot/utils/testsuites/utils/offsetstyle.test
  18. 21
      src/tests/shell/testsuites/punkexe/maketclpayloadcheck.test
  19. BIN
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates-0.2.0.tm
  20. 67
      src/vfs/_vfscommon.vfs/modules/punkboot/utils-0.6.0.tm

2
ARCHITECTURE.md

@ -86,7 +86,7 @@ Two layers with a deliberate dependency direction (the class never depends on th
- **punkcheck** records every install/delete as events in per-folder `.punkcheck` directories - the basis for skip/copy change detection, superseded-module pruning and provenance. Single OO record lifecycle (G-094, achieved) with atomic saves and advisory event-scoped locking for concurrent writers (G-095, achieved).
- **Provenance gates.** Build/promotion commands warn on uncommitted `src/` changes (column-0 `PROVENANCE-WARNING:` token, `-dirty-abort` for strict mode); `vendorupdate` warns for dirty source-project checkouts.
- **Boot-precondition gate (G-125).** A bake refuses a kit whose merged `.vfs` supplies no tcl library rather than deploying an artifact that cannot initialise: the kit lands in `FAILED KITS` and nothing is written, so the previously deployed `bin/<kit>` survives. Structural and non-executing (so cross-target kits are covered), reading the merged tree rather than the extraction outcome. The predicate is `punkboot::utils::vfs_boot_library_report`, called through the same guarded require as the provenance check so a stale bootsupport snapshot degrades it to a notice; `make.tcl check` reports ACTIVE/UNAVAILABLE. Sources: `src/AGENTS.md`, `src/modules/punkboot/utils-999999.0a1.0.tm`.
- **Payload/target consistency checks (G-133), both advisory.** At the same post-merge seam, a bake classifies each binary library in the merged tree by header (`punkboot::utils::binary_arch_classify` - PE/ELF/Mach-O, honest unknowns) against the kit's target platform; wrong-arch libraries outside platform-discriminated subdirs (canonical `<os>-<cpu>` names, vendor spellings like `win-x64`) earn recapped `BUILD-WARNING`s. After assembly, a kit declaring smoke-require packages (`mapvfs.config` 5th entry element) has each one plain-`package require`d inside the freshly built artifact via its tclsh subcommand - the only check that sees resolution-order defects (wrong-arch version shadowing); cross-target kits skip with a stated reason. Same guarded-require degradation; `make.tcl check` reports both. Neither guarantees statics, pure-tcl packages with binary deps, or version preference beyond the declared smoke set. Sources: `src/AGENTS.md`, `src/runtime/AGENTS.md`, `src/modules/punkboot/utils-999999.0a1.0.tm`.
- **Payload/target consistency checks (G-133 + G-134), all advisory.** At the same post-merge seam, a bake classifies each binary library in the merged tree by header (`punkboot::utils::binary_arch_classify` - PE/ELF/Mach-O, honest unknowns) against the kit's target platform; wrong-arch libraries outside platform-discriminated subdirs (canonical `<os>-<cpu>` names, vendor spellings like `win-x64`) earn recapped `BUILD-WARNING`s. After assembly, a kit declaring smoke-require packages (`mapvfs.config` 5th entry element) has each one plain-`package require`d inside the freshly built artifact via its tclsh subcommand - the only check that sees resolution-order defects (wrong-arch version shadowing); cross-target kits skip with a stated reason. The assembled image is also probed for its zip offset convention (G-134, `punkboot::utils::kit_offsetstyle_report` over `punk::zip::archive_info`): a FILE-relative attached payload warns (the pipeline emits archive-relative; the G-128 stamper refuses file-relative by default), no-zip/plain results stay silent. Same guarded-require degradation; `make.tcl check` reports all three. None guarantees statics, pure-tcl packages with binary deps, or version preference beyond the declared smoke set. Sources: `src/AGENTS.md`, `src/runtime/AGENTS.md`, `src/modules/punkboot/utils-999999.0a1.0.tm`.
- **Kit icon step (G-057 + G-128).** Every kit a bake builds gets a `<kitname>.resources.toml` sidecar (deployed beside `bin/<kit>`) recording the build-time icon choice - default `src/runtime/punkshell.ico`, overridable by a root `punkshell.ico` in the kit's own custom `.vfs` folder (pre-merge) - with sha256 identity and provenance lifted from the icon's G-135 assetorigin record. win32-target kits get the icon embedded as PE `RT_ICON`/`RT_GROUP_ICON` behind a single seam (`::punkboot::kit_icon_process`): the vendored punkres portable stamper (`src/tools/punkres`, built to `bin/punkres(.exe)` by the make.tcl tool step) is selected when present and works from any build host; otherwise the twapi arm (tcl-sfe mechanism) serves windows hosts with nothing built. Both arms share one semantic (delete all icon/group entries, write ids 1..N; the group is NAMED from the icon file's uppercased rootname - `PUNKSHELL` - with language adopted from the replaced group) and stamp a per-kit copy of the payload-free raw runtime prefix BEFORE payload attach; punkres can also stamp a FINISHED kit post-hoc, preserving an appended zip payload (archive-relative moved verbatim; file-relative refused by default, shifted with a consent flag). Non-PE targets skip as not applicable; a host where neither mechanism serves skips with a combined notice naming the punkres build remedy; the sidecar is written in every case. Sources: `src/AGENTS.md`, `bin/AGENTS.md`, `src/vfs/AGENTS.md`, `src/tools/AGENTS.md`.
- **Buildsuites and the kit family.** `src/buildsuites/suite_tcl90/` builds Tcl/Tk/tcllib from source with a pinned zig toolchain and produces the runtime kit family (plain / punk / bi) plus artifact metadata (G-096-G-117 era: see archived goals G-096, G-098, G-102, G-103, G-107); artifacts publish to the punkbin repo that `bin/punk-runtime.cmd` fetches from. `src/buildsuites/suite_tcl86/` is the 8.6 sibling (G-099 + G-100, both achieved): a Tcl 8.6 windows runtime (static + dynamic shells, `tcl86t.dll`, on-disk lib tree - no zipfs, so no self-contained kit) with thread 2.8 + tclvfs + Tk 8.6 + tklib + tcllib(+tcllibc critcl accelerators) companions, gated against the 8.6 core/thread/tclvfs testsuites with tcllib/tklib on a record tier and an opt-in `test-tk`; punkshell's own runtests is censused on that runtime at parity with a same-day native-8.6 baseline. Suite child shells scrub `TCL<major>_<minor>_TM_PATH` as well as TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY - without it a census measures the machine's module trees. The 8.6 kit container strategy remains in-flux - see "In-flux areas".
- **Project generation.** `dev project.new` composes thin layouts from `src/project_layouts/` (overlay chain with `.anti` deletion markers and `name@base` derivation - G-087, achieved) and injects bootsupport modules from the generating shell at generation time.

14
CHANGELOG.md

@ -5,6 +5,20 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.30.7] - 2026-07-29
- Baked kits pinned archive-relative (G-134): every assembled kit image is
probed post-assembly with `punk::zip::archive_info`
(`punkboot::utils::kit_offsetstyle_report`, punkboot::utils 0.6.0) and a
FILE-relative attached zip payload earns a recapped BUILD-WARNING -
advisory, the kit still builds and deploys. No-zip results (the metakit
kit shape) and bare zips stay silent; today's bakes warn on nothing
(measured baseline). `make.tcl check` reports the pin
ACTIVE/UNAVAILABLE with the same guarded-require degradation as the
G-125/G-133 checks. `punk::zip::mkzip -offsettype file` remains available
for deliberate manual file-relative artifacts - the pin asserts only what
the pipeline emits.
## [0.30.6] - 2026-07-29
- punkres is publishable as a punkbin artifact (G-128 distribution route):

4
GOALS-archive.md

@ -234,3 +234,7 @@ Acceptance: the `<asset-filename>.assetorigin.toml` sidecar format is documented
### G-128 [achieved 2026-07-29] Portable PE resource stamping: a vendored zig tool that icons a finished kit from any host → detail: goals/archive/G-128-portable-pe-resource-stamping.md
Scope: src/tools/punkres/ (vendored zig source - tool name settled in the work - with provenance and licence records); src/make.tcl (mechanism selection at the G-057 seam, sharing G-126's tool build step); bin/tools/zig* (pinned toolchain as consumed); bin/punkres.exe (untracked build output); src/tests/ (characterization - icon replaced, payload intact, idempotent, overlay-unsafe refusal); TEMP_REFERENCE/tcl-sfe (read-only reference for the resource structures)
Acceptance: the vendored tool, built by the same make.tcl tool step G-126 establishes, replaces RT_ICON/RT_GROUP_ICON in a pre-built PE that already carries an appended zip overlay, for the kit shapes we build (kit, zip, zipcat per mapvfs.config): the new icon is confirmed by reading the resources back with the tool ITSELF (so verification does not require a windows host or twapi) and cross-checked once on windows with twapi::extract_resources; the stamped kit still boots to a working punk shell reading its payload; re-stamping converges with no resource accumulation; it works where .rsrc is NOT the last section - the shape our own runtimes have (tclsfe-x64/punk91: .text .rdata .data .pdata .rsrc .reloc; suite-built tclsh9.0.5-punk: .text .rdata .buildid .data .pdata .tls .rsrc .reloc) - by appending a section and repointing the resource data directory rather than growing .rsrc in place; the appended payload survives the file-offset shift this causes, which is safe for an ARCHIVE-relative zip and not for a file-relative one; a file-relative payload is REFUSED BY DEFAULT with an error naming the reason and the rewrap remedy, but an explicit consent flag (tool CLI and the G-057 seam, never an interactive prompt in non-interactive runs) stamps it by shifting the overlay AND rewriting every central-directory local-header offset plus the EOCD directory offset by the shift delta - deliberately PRESERVING the file-relative convention, with a notification that the format is non-standard - verified by re-reading the stamped artifact (offsetstyle still file, members crc-verified); zip64 and multi-disk archives are refused even with consent; one end-to-end run from a NON-WINDOWS host produces a stamped win32 kit whose icon a windows machine confirms (recorded here: which host); G-057's seam selects the tool when present and falls back to its twapi path otherwise, with a parity check showing both mechanisms yield equivalent resource content from the same input, and zig stays optional - with no tool built, G-057 behaves exactly as it does without this goal; the tool's distribution is settled and recorded - buildable from the vendored source with the pinned toolchain AND publishable as a punkbin artifact through the G-123/G-006 channels, so a no-toolchain user has a route that does not depend on the committed twapi; the tool's structure handling is written so the parked RT_VERSION stamping needs no second tool; licensing and upstream provenance of the vendored tree are recorded per G-063 and G-026.
### G-134 [achieved 2026-07-29] Baked kits pinned archive-relative: advisory offset-style probe on assembled kit images → detail: goals/archive/G-134-baked-kits-pinned-archive-relative.md
Scope: src/make.tcl (post-assembly offset-style probe at the G-133 advisory-check seam, recapped reporting); src/modules/punkboot/utils-999999.0a1.0.tm (probe predicate beside the G-133 scan helpers, guarded require; bootsupport copies via established sync channels); src/tests/modules/punkboot/utils/ + src/tests/shell/testsuites/punkexe/ (characterization); src/AGENTS.md (what the pin does and does not guarantee)
Acceptance: each assembled kit image is probed with punk::zip::archive_info and a recapped BUILD-WARNING naming the kit is emitted when offsetstyle is file (advisory per the G-133 posture - the kit still builds and deploys, unlike the G-125 hard gate); plain and none results (no zip attached - the metakit kit shape G-131 targets) are silence, not warnings: the pin covers zip offset convention, not the presence of a zip; today's bakes produce no warning (G-124's measured baseline: every built kit archive-relative); the probe predicate lives in punkboot::utils beside the G-133 scan helpers, called through the same guarded require so a stale bootsupport snapshot degrades it to a NOTE, and tclsh src/make.tcl check reports its ACTIVE/UNAVAILABLE state; characterization pins the warning on a file-relative kit fixture and silence on the archive-relative kit set; scope is make.tcl's own assembly paths only - punk::zip::mkzip -offsettype file and modpod's equivalent REMAIN available for deliberate manual creation of file-relative artifacts (user decision 2026-07-27: the format works and serves naive zip-tooling/SFX-repair interop, it is simply not what our pipeline emits), and external zip-repair tools' file-relative output remains tolerated on input by the G-124 reader and normalized away by any re-bake.

4
GOALS.md

@ -399,10 +399,6 @@ Detail: goals/G-131-boot-payload-autodetection.md
Scope: TEMP_REFERENCE/tcl-windows-errno-catchall-TICKET-DRAFT.md (submission-ready ticket text - STAGING ONLY, unversioned, and the agent does not submit it); this detail file (the tracked record: finding, verbatim reproducer, the submitted URL once the user files it, and the upstream disposition); src/bootsupport/AGENTS.md and src/make.tcl (the existing workaround and its warning - dated, not removed, if upstream fixes it)
Detail: goals/G-132-tcl-windows-errno-catchall-report.md
### G-134 [active] Baked kits pinned archive-relative: advisory offset-style probe on assembled kit images
Scope: src/make.tcl (post-assembly offset-style probe at the G-133 advisory-check seam, recapped reporting); src/modules/punkboot/utils-999999.0a1.0.tm (probe predicate beside the G-133 scan helpers, guarded require; bootsupport copies via established sync channels); src/tests/modules/punkboot/utils/ + src/tests/shell/testsuites/punkexe/ (characterization); src/AGENTS.md (what the pin does and does not guarantee)
Detail: goals/G-134-baked-kits-pinned-archive-relative.md
### G-136 [proposed] Generated projects are seeded with a default icon from a single punkshell-held master
Scope: src/assets/logo/project-default/ (the single held seed artwork); src/modules/punk/mix/commandset/project-999999.0a1.0.tm (generation-time asset injection beside bootsupport_inject); src/project_layouts/ (per-layout icon-slot declaration with no artwork stored in layout trees); src/modules/punkcheck-999999.0a1.0.tm (install records for injected assets)
Detail: goals/G-136-generated-project-icon-seeding.md

8
bin/AGENTS.md

@ -149,7 +149,7 @@ and `tclsh9.1b0-tclsfe.exe` (modern `//zipfs:/app`; apnadkarni/tcl-sfe releases)
wrapped as `punkluck86` / `punk91ix86` in `src/runtime/mapvfs.config`. Characterization:
`src/tests/shell/testsuites/punkexe/kitmountpoint.test`.
### What a bake verifies about a deployed kit (G-125, G-133)
### What a bake verifies about a deployed kit (G-125, G-133, G-134)
A `bin/<kit>` that a bake deployed has passed the G-125 boot-precondition gate (its
merged vfs supplies a tcl library - a kit failing that is never deployed) and the
@ -158,7 +158,11 @@ wrong-arch `.dll`/`.so`/`.dylib` outside a platform-discriminated subdir earned
recapped `BUILD-WARNING` - advisory, so heed the bake recap before trusting a warned
kit) and, when its `src/runtime/mapvfs.config` entry declares smoke-require packages
and the kit is host-runnable, a plain `package require` of each inside the built
artifact via its tclsh subcommand. What that does NOT guarantee: statically linked
artifact via its tclsh subcommand. The assembled image was also probed for its zip
offset convention (G-134, advisory): a FILE-relative attached payload earned a
recapped `BUILD-WARNING` (the pipeline emits archive-relative; the G-128 stamper
refuses file-relative by default), while kits with no attached zip (metakit shapes)
probe as `none` and stay silent. What that does NOT guarantee: statically linked
packages and pure-tcl packages with binary dependencies are invisible to the arch
scan; package-resolution outcomes (a wrong-arch higher-versioned copy shadowing a
working one) are visible only to the smoke probe and only for the declared package

33
goals/G-134-baked-kits-pinned-archive-relative.md

@ -1,6 +1,6 @@
# G-134 Baked kits pinned archive-relative: advisory offset-style probe on assembled kit images
Status: active
Status: achieved 2026-07-29
Scope: src/make.tcl (post-assembly offset-style probe at the G-133 advisory-check seam, recapped reporting); src/modules/punkboot/utils-999999.0a1.0.tm (probe predicate beside the G-133 scan helpers, guarded require; bootsupport copies via established sync channels); src/tests/modules/punkboot/utils/ + src/tests/shell/testsuites/punkexe/ (characterization); src/AGENTS.md (what the pin does and does not guarantee)
Goal: every kit image make.tcl assembles is verified to carry an ARCHIVE-relative zip payload - probed with punk::zip::archive_info post-assembly - so the bake pipeline's archive-relative output property becomes a checked contract rather than a by-construction accident, and any future assembly path (new kit type, modpod or accelerator change) that emits file-relative offsets surfaces at bake time instead of when G-128's stamper refuses punkshell's own kits.
Acceptance: each assembled kit image is probed with punk::zip::archive_info and a recapped BUILD-WARNING naming the kit is emitted when offsetstyle is file (advisory per the G-133 posture - the kit still builds and deploys, unlike the G-125 hard gate); plain and none results (no zip attached - the metakit kit shape G-131 targets) are silence, not warnings: the pin covers zip offset convention, not the presence of a zip; today's bakes produce no warning (G-124's measured baseline: every built kit archive-relative); the probe predicate lives in punkboot::utils beside the G-133 scan helpers, called through the same guarded require so a stale bootsupport snapshot degrades it to a NOTE, and tclsh src/make.tcl check reports its ACTIVE/UNAVAILABLE state; characterization pins the warning on a file-relative kit fixture and silence on the archive-relative kit set; scope is make.tcl's own assembly paths only - punk::zip::mkzip -offsettype file and modpod's equivalent REMAIN available for deliberate manual creation of file-relative artifacts (user decision 2026-07-27: the format works and serves naive zip-tooling/SFX-repair interop, it is simply not what our pipeline emits), and external zip-repair tools' file-relative output remains tolerated on input by the G-124 reader and normalized away by any re-bake.
@ -71,3 +71,34 @@ asserts only what OUR pipeline emits by default.
semantic overlap with an offset-style probe). `goals_xref report` reviewed: no
unlinked pair concerns an output offset-convention check. The G-124/G-133 anchors are
archived; the relations above are from reading.
## Progress
- 2026-07-29 implemented and ACHIEVED same day (flip evidence): predicate
punkboot::utils::kit_offsetstyle_report landed (punkboot::utils 0.6.0, full
PUNKARGS argdoc; thin classification wrapper over punk::zip::archive_info -
offsetstyle archive|file|plain|none|unreadable + dataoffset + detail),
synced through make.tcl modules + bootsupport (a stale
src/bootsupport/.punkcheck.lock from a dead pid was removed per the
recorded recovery procedure en route) and promoted into _vfscommon via
vfscommonupdate (kits carry 0.6.0 from their next bake). make.tcl gained
get_kit_offsetstyle_report (same guarded-require shape as the G-125/G-133
getters), the post-assembly probe beside the smoke-require block (recapped
BUILD-WARNING only when offsetstyle is file; plain/none/unreadable silent;
NOTE when bootsupport is stale) and the 'check' report row ("kit
offset-style pin (G-134): ACTIVE (advisory)" with the contract lines).
Characterization: offsetstyle.test 7/7 (plain/archive/file fixtures built
hermetically with punk::zip::mkzip -runtime/-offsettype - the deliberately
available manual file-relative path exercised as the fixture generator -
plus none on text and metakit-magic binary, unreadable-with-detail on a
missing path, and the src/_build baseline sweep asserting no kit image
probes file); maketclpayloadcheck.test gains maketcl_check_offsetstyle_pin
(4/4 - status line + contract lines, ESC-free harness). Full utils suite
69/69; full punkexe suite 81 passed 0 failed. Live bake (punkluck86
rebuilt): probe silent - no NOTE (available), no BUILD-WARNING
(archive-relative), matching the acceptance's today's-bakes-produce-no-
warning clause. Docs per the acceptance: src/AGENTS.md gains the pin
bullet with the does/does-not-guarantee statement, bin/AGENTS.md's
deployed-kit section and ARCHITECTURE.md's check-family bullet extended.
punkshell 0.30.7. Every acceptance clause satisfied; remaining manual
items: none.

2
punkproject.toml

@ -1,4 +1,4 @@
[project]
name = "punkshell"
version = "0.30.6"
version = "0.30.7"
license = "BSD-2-Clause"

1
src/AGENTS.md

@ -73,6 +73,7 @@ Recovery after a wrong path guess:
- Uncommitted `make.tcl`-generated outputs may be batched into one commit, regardless of how many `make.tcl` invocations produced them. This covers punkcheck-managed build outputs that are VCS-tracked: `src/bootsupport/`, `_vfscommon.vfs/modules` + `/lib`, declared per-kit `*.vfs/lib_tcl<N>/<pkg>` subfolders, the thin-layout sync copies (`src/project_layouts/vendor/punk/<layout>/src/{make.tcl,build.tcl}` + bootsupport manifest copies), and the module-shipped layout payload under `src/modules/punk/mix/#modpod-templates-*/templates/project_layouts/` (G-087: synced from `src/project_layouts` - never hand-edit the modpod copy). (Root `modules/`, `lib/`, `modules_tcl<N>/`, `lib_tcl<N>/` are gitignored and never committed.) Each module's old→new version rename is atomic; a regeneration reflects one build-output refresh, not independent hand-edits. This is a carve-out from generic "split aggressively" commit guidance for punkcheck-managed build outputs only — it does not cover user-curated VFS content (`_config/`, per-kit `*.vfs/` outside declared `lib_tcl<N>` subfolders, `_aside/`, `mkzipfix.vfs`, `_vfscommon.vfs/doc`), which commits separately per its own concerns.
- **Boot-precondition gate (G-125).** A bake refuses any kit whose merged `.vfs` supplies no tcl library: the kit is listed under `FAILED KITS` with a reason naming the cause, and NOTHING is written - no `src/_build/<kit>`, no deploy, and the previously deployed `bin/<kit>` is left byte-identical. This is deliberately a failure rather than a warning: the deploy step deletes the old kit before copying the new one, so the pre-gate behaviour replaced a working shell with an artifact that died at startup with `application-specific initialization failed: Cannot find a usable init.tcl`. The check is structural and non-executing (so it covers cross-target kits too): `init.tcl` in `tcl_library/`, `lib/tcl<major>.<minor>/` or `tcl<major>.<minor>/` - the third for runtimes whose archive mounts at the executable's own path rather than `//zipfs:/app`, so `[info library]` is `<exe>/tcl8.6` (the androwish/undroidwish zipfs backport for 8.6 does this) - plus one companion file a real Tcl library carries beside it (`tm.tcl`, `package.tcl`, `auto.tcl`, `clock.tcl`, `history.tcl`, `word.tcl`) or an `encoding/` directory - without that second test the `lib/BWidget1.10.1/init.tcl` every punkshell kit carries would answer for a tcl library it cannot provide. It reads the MERGED tree rather than asking whether extraction ran, so a `.vfs` that legitimately supplies its own tcl library (`src/vfs/punk8_statictwapi.vfs`, `src/vfs/punk9test.vfs`) builds and deploys unchanged. **Remedy when a kit is refused:** the usual cause is that the runtime's own payload could not be extracted - read the recapped `BUILD-WARNING` naming what was tried, then either fix extraction (a driving tclsh with no zipfs needs punk::zip >= 0.2.0 in bootsupport - see the extraction bullet above), point the kit at a runtime that carries a payload, or have the kit's `.vfs` supply a tcl library itself. The predicate is `punkboot::utils::vfs_boot_library_report` called through a guarded require, exactly as the dirty-src provenance check is: a stale or missing bootsupport snapshot degrades the gate to a `NOTE` rather than failing every kit. `tclsh src/make.tcl check` reports whether the gate is `ACTIVE` or `UNAVAILABLE`.
- **Payload/target consistency checks (G-133), both ADVISORY** - warnings are recapped `BUILD-WARNING`s and the kit still builds and deploys (unlike the G-125 gate). (a) **Binary-arch scan**, at the same post-merge seam as the gate: every binary library (`*.dll`/`*.so`/`*.dylib`) in the merged tree is classified by header bytes (`punkboot::utils::binary_arch_classify` - PE Machine field, ELF e_machine, Mach-O incl. universal; unclassifiable content is honestly `unknown` and never warned about) against the kit's target platform. Structural and non-executing, so it runs for cross-target kits too (measured ~30-140ms per kit, avg ~93ms across the 12 assembled trees). Libraries under a platform-discriminated subdir - canonical `<os>-<cpu>` platform-dir names, the universal `macosx` folder, or a recognised vendor spelling (`win-x64` etc; the lists are namespace variables in `punkboot::utils`) - are exempt: multi-arch payloads are legitimate there. First real sweep (2026-07-27) found a true latent instance: `zint.dll` 2.13.0 in `src/vfs/punk8win.vfs/lib_tcl8/` is 32-bit (i386) and can never load in the x64 tcl8 kits (punk86/punkbi/punksys) that carry it - those bakes warn until the payload is fixed. (b) **Smoke-require probe**: a kit declaring packages in `src/runtime/mapvfs.config` (5th entry element - see `src/runtime/AGENTS.md`) has each one plain-`package require`d INSIDE the freshly built artifact via its tclsh subcommand, which exits cleanly even on runtimes whose full repl teardown is fragile. This is the only check that observes actual package RESOLUTION - the punkluck86 2026-07-27 incident class (a higher-versioned wrong-arch Thread shadowing the runtime's working copy on plain require) is invisible to structural checks because the working file exists too. Failures are recapped naming kit, package and the actual error; cross-target kits skip with a stated reason; undeclared kits run nothing new; the probe only runs when the kit actually rebuilds. **What the checks do NOT guarantee:** statically linked packages, pure-tcl packages whose binary dependency lives elsewhere, and version-preference outcomes are visible only to the smoke probe - and only for the declared package set; the arch scan proves nothing about loadability beyond architecture, and an `unknown` classification is silence, not a pass. Both degrade to a NOTE when the bootsupport `punkboot::utils` snapshot is stale (same guarded require as the G-125 gate); `tclsh src/make.tcl check` reports the scan's ACTIVE/UNAVAILABLE state and the currently declared smoke-require kits.
- **Kit offset-style pin (G-134), ADVISORY** - at the same seam family, every ASSEMBLED kit image is probed with `punk::zip::archive_info` (via `punkboot::utils::kit_offsetstyle_report`, same guarded-require degradation to a NOTE; `make.tcl check` reports ACTIVE/UNAVAILABLE): a FILE-relative attached zip payload earns a recapped `BUILD-WARNING` naming the kit, because the pipeline emits ARCHIVE-relative by construction and the G-128 stamper refuses file-relative by default - a warning here means an assembly path regressed the output convention. **What the pin does NOT cover:** `plain` and `none` results (a bare zip, or no attached zip at all - the metakit kit shape, and any unreadable input) are silence, never warnings - the pin asserts the offset CONVENTION of an attached zip, not the presence of one; and it asserts only what make.tcl's own assembly paths EMIT - `punk::zip::mkzip -offsettype file` (and modpod's equivalent) remain available for deliberate manual creation of file-relative artifacts, and file-relative INPUT artifacts remain tolerated by the G-124 reader and normalized by any re-bake.
- Kit bakes consume the punk-runtime WORKING COPIES under `bin/runtime/<platform>/`: `make.tcl bake`/`bakehouse` emit a `BUILD-WARNING:` (recapped at end of run) when a wrapped runtime's beside-toml revision is older than an `-r<N>` artifact present in the same folder - the forgot-to-switch-back guard for deliberate `punk-runtime use <old-rN>` testing excursions. Heed it before trusting freshly baked family kits; `bin/punk-runtime.cmd use <artifact-r<N>>` (`.exe` suffix optional) re-materializes the working copy.
- `tclsh src/make.tcl bakelist ?kitname ...?` (G-121) reports the kit outputs configured in `src/runtime/mapvfs.config`: kit name, kit type, runtime (with presence in the runtime store), vfs folder, and the deployed state of `bin/<kit>` vs the `src/_build` build product (`current|stale|absent|nobuild`), with anomalies (`runtime=missing`, `vfs=missing`, `rtrev=r<cur><r<max>` materialization staleness) and the cross-target marker `target=<platform>` in a trailing notes column. A nonexistent store tier is flagged loudly per tier actually referenced (header `(FOLDER MISSING)` for the default tier + stderr derivation; bake's no-runtimes exit self-diagnoses the same way). Name arguments filter the report and add a per-kit detail block (resolved store tier, target and provenance of the target, paths/sizes/mtimes). `make.tcl bake ?kitname ...?` bakes and deploys only the named kits - other kits' `_build`/`bin` artifacts and punkcheck records are untouched and the vfslibs phase narrows to the named kits' vfs folders; an unknown name errors before any build, listing the configured names; flags go before kit names (`make.tcl bake -confirm 0 punk91`). Bare `bake` processes all configured kits as before. Both surfaces consume the shared parsed-mapping helpers (`::punkboot::lib::mapvfs_*`) rather than the file format, so the G-024 config-format work swaps the reader underneath. Piped characterization: `src/tests/shell/testsuites/punkexe/maketclbakelist.test`.
- **Host vs target platform (G-122).** Everything a bake EMITS is keyed by the artifact's TARGET platform, never by the driving tclsh's personality: the `bin/runtime/<tier>` store the runtime is read from, `.exe` suffixing of runtime files and kit outputs, the presence checks behind `runtime=missing`, and whether the pre-deploy process sweep uses `tasklist`/`taskkill` or `ps`/`kill`. Host semantics - copy commands, path handling, filesystem case rules, prompts - stay keyed to the host. The default target is the host's own platform canon, EXCEPT a cygwin-family host (an msys2/cygwin-runtime tclsh, which reports `tcl_platform(platform)` `unix` on windows and canonizes as `msys-x86_64`/`cygwin-x86_64`), which targets `win32-x86_64`: such a host now drives the identical kit set, names and store addressing as a native tclsh. `tclsh src/make.tcl check` prints the derivation on one `platform (G-122): host=... target=... store=... exe-suffix=... process-tooling=...` line. A mapvfs entry may declare its own target platform (4th element - see `src/runtime/AGENTS.md`), which is then read from that platform's tier, named with that platform's executable convention, and skipped by the process sweep (its processes are not visible to this host). Traps this closes, all field-verified 2026-07-26: msys `ps` cannot see a natively-launched kit (only `tasklist` can), and msys2 rewrites arguments that look like absolute posix paths when spawning a native windows program - so `taskkill /PID <n>` arrived as `taskkill C:/<msysroot>/PID <n>`; native-windows command lines therefore go through `::punkboot::exec_nativeargs` (sets `MSYS2_ARG_CONV_EXCL=*`, inert elsewhere). Also note a cygwin-family tclsh is a POSIX Tcl: name the script the way that shell spells paths (`tclsh /c/repo/.../src/make.tcl`, not `C:/repo/...`, which it resolves relative to the cwd) - make.tcl diagnoses the windows-spelling case. Piped characterization: `src/tests/shell/testsuites/punkexe/maketclplatform.test` (the cygwin-host tests self-gate on finding and probing a real msys/cygwin tclsh; `PUNK_MSYS_TCLSH` names one explicitly).

BIN
src/bootsupport/modules/punk/mix/templates-0.2.0.tm

Binary file not shown.

67
src/bootsupport/modules/punkboot/utils-0.5.0.tm → src/bootsupport/modules/punkboot/utils-0.6.0.tm

@ -7,7 +7,7 @@
# (C) 2023
#
# @@ Meta Begin
# Application punkboot::utils 0.5.0
# Application punkboot::utils 0.6.0
# Meta platform tcl
# Meta license BSD
# @@ Meta End
@ -23,7 +23,7 @@ package require Tcl 8.6-
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punkboot::utils {
variable version 0.1.0
namespace export binary_arch_classify parse_punkproject_version read_punkproject_version read_changelog_latest_version vcs_dirty_warnings vfs_binary_arch_report vfs_boot_library_report
namespace export binary_arch_classify kit_offsetstyle_report parse_punkproject_version read_punkproject_version read_changelog_latest_version vcs_dirty_warnings vfs_binary_arch_report vfs_boot_library_report
namespace eval argdoc {
@ -736,6 +736,65 @@ namespace eval punkboot::utils {
dict set report ok [expr {[llength $mismatches] == 0}]
return $report
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punkboot::utils::kit_offsetstyle_report
@cmd -name "::punkboot::utils::kit_offsetstyle_report"\
-summary\
"Advisory zip offset-style probe of an assembled kit image (G-134)"\
-help\
"Probes a finished kit image file with punk::zip::archive_info and
reports the offset convention of any attached zip payload. The
punkshell bake pipeline emits ARCHIVE-relative payloads by
construction; this probe makes that a checked contract - a
'file' result is the pipeline-regression signal make.tcl
surfaces as a recapped BUILD-WARNING (advisory: the kit still
builds and deploys). 'plain' (the file is a bare zip), 'none'
(no zip attached - e.g the metakit kit shape, or any non-zip
file) and 'unreadable' are silence, not warnings: the pin
covers the offset convention of an attached zip, never the
presence of one. Purely structural: one EOCD scan plus a
central-directory walk, nothing executed, so cross-target kit
images are covered.
Returned dict keys:
offsetstyle archive | file | plain | none | unreadable
dataoffset payload base offset when archive_info supplies
one (\"\" otherwise)
detail diagnostic text for unreadable (missing file,
punk::zip unavailable, archive_info error);
empty otherwise"
@leaders
kitpath -type string -optional 0 -help\
"Path of the assembled kit image file to probe"
}]
}
proc kit_offsetstyle_report {kitpath} {
set report [dict create offsetstyle unreadable dataoffset "" detail ""]
if {![file isfile $kitpath]} {
dict set report detail "no such file: $kitpath"
return $report
}
if {[catch {package require punk::zip} errM]} {
dict set report detail "punk::zip not available: $errM"
return $report
}
if {[catch {punk::zip::archive_info $kitpath} zinfo]} {
dict set report detail "archive_info error: $zinfo"
return $report
}
if {[dict exists $zinfo offsetstyle]} {
dict set report offsetstyle [dict get $zinfo offsetstyle]
} else {
dict set report detail "archive_info returned no offsetstyle"
return $report
}
if {[dict exists $zinfo dataoffset]} {
dict set report dataoffset [dict get $zinfo dataoffset]
}
return $report
}
}
namespace eval ::punk::args::register {
@ -746,8 +805,8 @@ namespace eval ::punk::args::register {
## Ready
package provide punkboot::utils [tcl::namespace::eval punkboot::utils {
variable version
#- this version number, exactly 0.5.0, is a literal used in src module folders
#- this version number, exactly 0.6.0, is a literal used in src module folders
#- we refer to this sometimes as the magic version number
set version 0.5.0
set version 0.6.0
}]
return

43
src/make.tcl

@ -3478,6 +3478,20 @@ proc ::punkboot::get_vfs_binary_arch_report {vfsfolder targetplatform} {
}
return [list 1 [::punkboot::utils::vfs_binary_arch_report $vfsfolder $targetplatform]]
}
#Availability probe + advisory zip offset-style check of an assembled kit image
#(G-134), shared by the bake loop and the 'check' command report. Same guarded-
#require treatment as the checks above: a stale bootsupport snapshot degrades
#the pin to a NOTE. Returns {available report}.
proc ::punkboot::get_kit_offsetstyle_report {kitpath} {
set available [expr {\
![catch {package require punkboot::utils}]\
&& [llength [info commands ::punkboot::utils::kit_offsetstyle_report]]\
}]
if {!$available} {
return [list 0 [dict create]]
}
return [list 1 [::punkboot::utils::kit_offsetstyle_report $kitpath]]
}
#G-133 smoke-require probe: execute a freshly built kit artifact via its 'tclsh'
#subcommand and plain-'package require' each declared package inside it. This is the
#only check that observes actual package RESOLUTION in the artifact - the punkluck86
@ -3823,6 +3837,20 @@ if {$::punkboot::command eq "check"} {
puts stdout "payload/target arch scan (G-133): UNAVAILABLE (punkboot::utils vfs_binary_arch_report not loadable from bootsupport)"
puts stdout " bake will proceed with a NOTE and cannot flag wrong-arch payload libraries - run 'make.tcl modules' then 'make.tcl bootsupport'."
}
# Kit offset-style pin (G-134) - advisory archive-relative output contract
puts stdout $sep
#availability probe only - the path deliberately does not exist
lassign [::punkboot::get_kit_offsetstyle_report [file join $projectroot __punkboot_offsetstyle_probe__]] _off_available _off_report
if {$_off_available} {
puts stdout "kit offset-style pin (G-134): ACTIVE (advisory)"
puts stdout " bake probes each assembled kit image with punk::zip::archive_info and emits a recapped"
puts stdout " BUILD-WARNING when an attached zip payload records FILE-relative offsets (the pipeline"
puts stdout " emits archive-relative by construction; the G-128 stamper refuses file-relative by default)."
puts stdout " plain/none results (no attached zip - e.g the metakit kit shape) are silence, not warnings."
} else {
puts stdout "kit offset-style pin (G-134): UNAVAILABLE (punkboot::utils kit_offsetstyle_report not loadable from bootsupport)"
puts stdout " bake will proceed with a NOTE and cannot flag a file-relative kit payload - run 'make.tcl modules' then 'make.tcl bootsupport'."
}
set _smoke_declared [list]
if {[file exists $sourcefolder/runtime/mapvfs.config]} {
set _smokemodel [punkboot::lib::mapvfs_parse $sourcefolder/runtime/mapvfs.config [file dirname $sourcefolder]/bin/runtime $sourcefolder $::punkboot::target_platform]
@ -7662,6 +7690,21 @@ foreach vfstail $vfs_tails {
# -- --- --- --- --- ---
$vfs_event targetset_end OK
#G-134 post-assembly offset-style pin: the pipeline emits
#ARCHIVE-relative zip payloads by construction - this probe makes
#that a checked contract. Advisory per the G-133 posture: a
#file-relative image earns a recapped BUILD-WARNING (the G-128
#stamper would refuse it by default) and the kit still builds and
#deploys; plain/none (no attached zip - the metakit kit shape) and
#unreadable results are silence. One EOCD scan + CD walk per
#REBUILT kit only.
lassign [::punkboot::get_kit_offsetstyle_report $buildfolder/$targetkit] offprobe_available offprobe
if {!$offprobe_available} {
puts stderr "NOTE: kit offset-style pin unavailable (punkboot::utils kit_offsetstyle_report not loadable from bootsupport) - continuing without it"
} elseif {[dict get $offprobe offsetstyle] eq "file"} {
::punkboot::print_build_warnings [list "kit $targetkit assembled with a FILE-relative zip payload (G-134 pin: the pipeline emits archive-relative by construction; the G-128 stamper refuses file-relative by default) - investigate this kit's assembly path"]
}
#G-133 post-build smoke-require probe. Only an actual 'package require'
#inside the built artifact observes package RESOLUTION - the wrong-arch
#version-shadowing class is invisible to every structural check because

560
src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl vendored

@ -2300,15 +2300,24 @@ DIAGRAM 2 - KIT ASSEMBLY DETAIL (the 'make.tcl bake' stage; incl. vfslibs phase)
(gate) --> refused: kit listed under FAILED KITS, nothing
| built, nothing deployed, bin/<kit> untouched
|
| zip image appended to the runtime exe: tcl::zipfs::mkimg,
| or (driving tcl without zipfs, e.g 8.6) raw-runtime split
| + punk::zip::mkzip + concatenation - both mount the same
| kit icon step (G-057): <kit>.resources.toml sidecar records
| the icon choice for every target (default src/runtime/
| punkshell.ico; override = punkshell.ico in the kit's own .vfs
| folder, pre-merge); win32 targets on a twapi-capable host get
| the icon stamped into a per-kit copy of the raw runtime stub
| BEFORE the payload attach (non-PE target: not applicable;
| incapable host: distinct notice - sidecar written either way)
|
| zip image appended to the (stamped) runtime exe: tcl::zipfs::
| mkimg, or (driving tcl without zipfs, e.g 8.6) raw-runtime
| split + punk::zip::mkzip + concatenation - both mount the same
v
src/_build/<kit>.exe
src/_build/<kit>.exe (+ <kit>.exe.resources.toml)
|
| smoke-require probe (host-runnable kits with declared
| packages): plain 'package require' inside the artifact [K11]
| deploy step: delete old bin/<kit>.exe, copy new one in
| (the resource sidecar deploys beside the kit)
v
bin/<kit>.exe <-- FAILS (kit listed under FAILED KITS) if a
running shell still holds the old exe [K5]
@ -3469,6 +3478,20 @@ proc ::punkboot::get_vfs_binary_arch_report {vfsfolder targetplatform} {
}
return [list 1 [::punkboot::utils::vfs_binary_arch_report $vfsfolder $targetplatform]]
}
#Availability probe + advisory zip offset-style check of an assembled kit image
#(G-134), shared by the bake loop and the 'check' command report. Same guarded-
#require treatment as the checks above: a stale bootsupport snapshot degrades
#the pin to a NOTE. Returns {available report}.
proc ::punkboot::get_kit_offsetstyle_report {kitpath} {
set available [expr {\
![catch {package require punkboot::utils}]\
&& [llength [info commands ::punkboot::utils::kit_offsetstyle_report]]\
}]
if {!$available} {
return [list 0 [dict create]]
}
return [list 1 [::punkboot::utils::kit_offsetstyle_report $kitpath]]
}
#G-133 smoke-require probe: execute a freshly built kit artifact via its 'tclsh'
#subcommand and plain-'package require' each declared package inside it. This is the
#only check that observes actual package RESOLUTION in the artifact - the punkluck86
@ -3814,6 +3837,20 @@ if {$::punkboot::command eq "check"} {
puts stdout "payload/target arch scan (G-133): UNAVAILABLE (punkboot::utils vfs_binary_arch_report not loadable from bootsupport)"
puts stdout " bake will proceed with a NOTE and cannot flag wrong-arch payload libraries - run 'make.tcl modules' then 'make.tcl bootsupport'."
}
# Kit offset-style pin (G-134) - advisory archive-relative output contract
puts stdout $sep
#availability probe only - the path deliberately does not exist
lassign [::punkboot::get_kit_offsetstyle_report [file join $projectroot __punkboot_offsetstyle_probe__]] _off_available _off_report
if {$_off_available} {
puts stdout "kit offset-style pin (G-134): ACTIVE (advisory)"
puts stdout " bake probes each assembled kit image with punk::zip::archive_info and emits a recapped"
puts stdout " BUILD-WARNING when an attached zip payload records FILE-relative offsets (the pipeline"
puts stdout " emits archive-relative by construction; the G-128 stamper refuses file-relative by default)."
puts stdout " plain/none results (no attached zip - e.g the metakit kit shape) are silence, not warnings."
} else {
puts stdout "kit offset-style pin (G-134): UNAVAILABLE (punkboot::utils kit_offsetstyle_report not loadable from bootsupport)"
puts stdout " bake will proceed with a NOTE and cannot flag a file-relative kit payload - run 'make.tcl modules' then 'make.tcl bootsupport'."
}
set _smoke_declared [list]
if {[file exists $sourcefolder/runtime/mapvfs.config]} {
set _smokemodel [punkboot::lib::mapvfs_parse $sourcefolder/runtime/mapvfs.config [file dirname $sourcefolder]/bin/runtime $sourcefolder $::punkboot::target_platform]
@ -6296,6 +6333,472 @@ proc ::punkboot::assemble_zipcat_image {raw_runtime wrapvfs outfile zipfile} {
return $zipbytes
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#G-057 kit icon step. Every built kit gets a <kitname>.resources.toml sidecar
#recording the build-time icon choice (default src/runtime/punkshell.ico,
#overridable by a root-level punkshell.ico in the kit's own custom .vfs folder,
#consulted PRE-merge so the _vfscommon copy never reads as an override), and
#win32-target kits get that icon embedded as PE RT_ICON/RT_GROUP_ICON resources
#when this host can write them. Two mechanisms share one semantic (delete ALL
#icon/group entries, write the .ico images as ids 1..N; the RT_GROUP_ICON is
#NAMED from the icon file - uppercased rootname, so punkshell.ico gives
#PUNKSHELL (user decision 2026-07-29, RC uppercase convention) - with language
#adopted from the first replaced group; idempotent by construction, parity
#recorded in the G-128 detail file): the vendored punkres portable stamper
#(G-128, src/tools/punkres, built by 'make.tcl tool build punkres') is
#selected when bin/punkres(.exe) exists and works from ANY build host;
#otherwise the twapi arm (tcl-sfe sfe-0.2.tm ReplaceIconInStub lineage; Ashok
#P. Nadkarni) serves windows hosts with nothing built. Ordering is stub-first either way: a per-kit
#copy of the payload-free raw runtime prefix is stamped BEFORE any payload is
#appended (punkres could also stamp post-hoc - the bake keeps stub-first so
#both arms stay interchangeable). kit_icon_process is the single internal
#entry point - mechanism selection lives inside it. Non-PE targets skip as NOT
#APPLICABLE; a win32 target on an incapable host skips with a distinct notice;
#both still write the sidecar.
#Decisions and skip taxonomy: goals G-057 detail file. Sidecar format doc:
#bin/AGENTS.md.
#sha256 is self-contained (same algorithm as src/assets/logo/make-ico.tcl and
#scriptlib/developer/assetorigin_check.tcl - each stays standalone: make.tcl
#must not depend on scriptlib, and this step must not depend on bootsupport
#freshness; bootsupport carries only sha1/md5). Memoized per path per run.
namespace eval ::punkboot::kiticon {
variable sha256_cache
if {![info exists sha256_cache]} {set sha256_cache [dict create]}
variable twapi_state ""
variable punkres_state ""
variable K {
0x428a2f98 0x71374491 0xb5c0fbcf 0xe9b5dba5 0x3956c25b 0x59f111f1 0x923f82a4 0xab1c5ed5
0xd807aa98 0x12835b01 0x243185be 0x550c7dc3 0x72be5d74 0x80deb1fe 0x9bdc06a7 0xc19bf174
0xe49b69c1 0xefbe4786 0x0fc19dc6 0x240ca1cc 0x2de92c6f 0x4a7484aa 0x5cb0a9dc 0x76f988da
0x983e5152 0xa831c66d 0xb00327c8 0xbf597fc7 0xc6e00bf3 0xd5a79147 0x06ca6351 0x14292967
0x27b70a85 0x2e1b2138 0x4d2c6dfc 0x53380d13 0x650a7354 0x766a0abb 0x81c2c92e 0x92722c85
0xa2bfe8a1 0xa81a664b 0xc24b8b70 0xc76c51a3 0xd192e819 0xd6990624 0xf40e3585 0x106aa070
0x19a4c116 0x1e376c08 0x2748774c 0x34b0bcb5 0x391c0cb3 0x4ed8aa4a 0x5b9cca4f 0x682e6ff3
0x748f82ee 0x78a5636f 0x84c87814 0x8cc70208 0x90befffa 0xa4506ceb 0xbef9a3f7 0xc67178f2
}
}
proc ::punkboot::kiticon::sha256_data {data} {
variable K
set bitlen [expr {wide([string length $data]) * 8}]
append data \x80
set pad [expr {(56 - [string length $data] % 64 + 64) % 64}]
append data [string repeat \x00 $pad] [binary format W $bitlen]
set h0 0x6a09e667; set h1 0xbb67ae85; set h2 0x3c6ef372; set h3 0xa54ff53a
set h4 0x510e527f; set h5 0x9b05688c; set h6 0x1f83d9ab; set h7 0x5be0cd19
set n [string length $data]
for {set off 0} {$off < $n} {incr off 64} {
binary scan [string range $data $off [expr {$off + 63}]] Iu16 w
for {set t 16} {$t < 64} {incr t} {
set x [lindex $w [expr {$t - 15}]]
set s0 [expr {((($x >> 7) | ($x << 25)) ^ (($x >> 18) | ($x << 14)) ^ ($x >> 3)) & 0xffffffff}]
set x [lindex $w [expr {$t - 2}]]
set s1 [expr {((($x >> 17) | ($x << 15)) ^ (($x >> 19) | ($x << 13)) ^ ($x >> 10)) & 0xffffffff}]
lappend w [expr {([lindex $w [expr {$t - 16}]] + $s0 + [lindex $w [expr {$t - 7}]] + $s1) & 0xffffffff}]
}
set a $h0; set b $h1; set c $h2; set d $h3
set e $h4; set f $h5; set g $h6; set h $h7
for {set t 0} {$t < 64} {incr t} {
set S1 [expr {((($e >> 6) | ($e << 26)) ^ (($e >> 11) | ($e << 21)) ^ (($e >> 25) | ($e << 7))) & 0xffffffff}]
set ch [expr {(($e & $f) ^ (~$e & $g)) & 0xffffffff}]
set T1 [expr {($h + $S1 + $ch + [lindex $K $t] + [lindex $w $t]) & 0xffffffff}]
set S0 [expr {((($a >> 2) | ($a << 30)) ^ (($a >> 13) | ($a << 19)) ^ (($a >> 22) | ($a << 10))) & 0xffffffff}]
set maj [expr {($a & $b) ^ ($a & $c) ^ ($b & $c)}]
set T2 [expr {($S0 + $maj) & 0xffffffff}]
set h $g; set g $f; set f $e; set e [expr {($d + $T1) & 0xffffffff}]
set d $c; set c $b; set b $a; set a [expr {($T1 + $T2) & 0xffffffff}]
}
set h0 [expr {($h0 + $a) & 0xffffffff}]; set h1 [expr {($h1 + $b) & 0xffffffff}]
set h2 [expr {($h2 + $c) & 0xffffffff}]; set h3 [expr {($h3 + $d) & 0xffffffff}]
set h4 [expr {($h4 + $e) & 0xffffffff}]; set h5 [expr {($h5 + $f) & 0xffffffff}]
set h6 [expr {($h6 + $g) & 0xffffffff}]; set h7 [expr {($h7 + $h) & 0xffffffff}]
}
return [format %08x%08x%08x%08x%08x%08x%08x%08x $h0 $h1 $h2 $h3 $h4 $h5 $h6 $h7]
}
proc ::punkboot::kiticon::sha256_file {path} {
variable sha256_cache
set key [file normalize $path]
if {[dict exists $sha256_cache $key]} {
return [dict get $sha256_cache $key]
}
set f [open $path rb]
set data [read $f]
close $f
set hex [sha256_data $data]
dict set sha256_cache $key $hex
return $hex
}
proc ::punkboot::kiticon::toml_escape {s} {
return [string map {\\ \\\\ \" \\\"} $s]
}
#Tolerant subset reader for the chosen icon's G-135 assetorigin sidecar - lifts
#source/source_hash so the kit record reaches back to the SVG-derived master.
#Missing sidecar, unknown schema or malformed content simply yields no
#provenance fields (never an error).
proc ::punkboot::kiticon::assetorigin_read {assetpath} {
set sc $assetpath.assetorigin.toml
set out [dict create]
if {![file isfile $sc]} {
return $out
}
if {[catch {
set f [open $sc rb]
set raw [read $f]
close $f
}]} {
return $out
}
foreach line [split [encoding convertfrom utf-8 $raw] \n] {
set line [string trim [string map [list \r {}] $line]]
if {$line eq "" || [string index $line 0] eq "#"} continue
if {[string index $line 0] eq "\["} break
set eq [string first = $line]
if {$eq < 1} continue
set key [string trim [string range $line 0 [expr {$eq - 1}]]]
if {$key ni {schema source source_hash}} continue
set val [string trim [string range $line [expr {$eq + 1}] end]]
if {[string index $val 0] eq "\""} {
set parsed ""
set ok 0
set len [string length $val]
for {set i 1} {$i < $len} {incr i} {
set chr [string index $val $i]
if {$chr eq "\\"} {
incr i
append parsed [string index $val $i]
} elseif {$chr eq "\""} {
set ok 1
break
} else {
append parsed $chr
}
}
if {!$ok} continue
set val $parsed
} else {
set cmt [string first # $val]
if {$cmt >= 0} {
set val [string trim [string range $val 0 [expr {$cmt - 1}]]]
}
}
dict set out $key $val
}
if {![dict exists $out schema] || [dict get $out schema] ne "1"} {
return [dict create]
}
return $out
}
proc ::punkboot::kiticon::writefile_ifchanged {path content} {
if {[file exists $path]} {
set f [open $path rb]
set existing [read $f]
close $f
if {$existing eq $content} {
return unchanged
}
}
set f [open $path wb]
puts -nonewline $f $content
close $f
return written
}
proc ::punkboot::kiticon::copy_ifchanged {from to} {
if {![file isfile $from]} {
return absent
}
set f [open $from rb]
set fromdata [read $f]
close $f
if {[file exists $to]} {
set f [open $to rb]
set todata [read $f]
close $f
if {$todata eq $fromdata} {
return unchanged
}
}
file copy -force $from $to
return copied
}
#Memoized once per run: can this host's tclsh write PE resources? Returns
#{1 {}} or {0 reason} with the reason distinguishing cross-host from
#twapi-unavailable (the acceptance requires distinct notices).
proc ::punkboot::kiticon::twapi_available {} {
variable twapi_state
if {$twapi_state eq "ok"} {
return [list 1 {}]
}
if {$twapi_state ne ""} {
return [list 0 $twapi_state]
}
if {[punkboot::lib::platform_process_family $::punkboot::host_platform] ne "windows"} {
set twapi_state "cross-host: win32 target baked on a $::punkboot::host_platform host cannot run the twapi resource APIs (portable post-hoc stamping is the G-128 remedy)"
return [list 0 $twapi_state]
}
if {[catch {package require twapi_resource}] && [catch {package require twapi}]} {
set twapi_state "twapi resource APIs not loadable in this tclsh ([info nameofexecutable]) - the vendored twapi under vendorlib_tcl<N>/win32-x86_64 serves native windows tclsh builds"
return [list 0 $twapi_state]
}
if {![llength [info commands ::twapi::begin_resource_update]]} {
set twapi_state "twapi loaded but its resource-update APIs are absent"
return [list 0 $twapi_state]
}
set twapi_state ok
return [list 1 {}]
}
#Memoized once per run: locate the built punkres portable stamper (G-128).
#Returns {1 <exepath>} or {0 <reason>}. Presence of the binary IS the
#mechanism-selection signal: with no tool built the twapi arm and its skip
#taxonomy behave exactly as they did before G-128 (zig stays optional).
proc ::punkboot::kiticon::punkres_available {repo_root} {
variable punkres_state
if {$punkres_state ne ""} {
return $punkres_state
}
if {[punkboot::lib::platform_process_family $::punkboot::host_platform] eq "windows"} {
set exe $repo_root/bin/punkres.exe
} else {
set exe $repo_root/bin/punkres
}
if {[file isfile $exe]} {
set punkres_state [list 1 $exe]
} else {
set punkres_state [list 0 "portable stamper not present at $exe - 'tclsh src/make.tcl tool build punkres' builds it from vendored source, or fetch the prebuilt punkbin artifact (win32-x86_64/tools/punkres-*; route: src/tools/punkres/PROVENANCE.md) (G-128)"]
}
return $punkres_state
}
#G-128 punkres arm: exec the vendored portable stamper on the payload-free stub
#copy. punkres itself deletes all icon/group entries, writes the images as ids
#1..N under the first pre-existing group's name+lang (a non-empty
#group_override renames the group via 'set-icon -group' - the seam's naming
#policy; language and codepage adoption unchanged), and re-reads its own
#output (resources AND any overlay) before reporting success - equivalent
#resource content to the twapi arm from the same input (parity evidence in the
#G-128 detail file). Returns the number of images embedded; raises an error
#carrying punkres's output on any nonzero exit.
proc ::punkboot::kit_icon_embed_punkres {punkres_exe exepath icopath {group_override {}}} {
set f [open $icopath rb]
set icondata [read $f]
close $f
binary scan $icondata ttt ico_reserved ico_type ico_count
if {$ico_reserved != 0 || $ico_type != 1} {
error "$icopath is not recognisable as a .ico file"
}
if {$ico_count == 0} {
error "no images in $icopath"
}
set cmd [list exec [file nativename $punkres_exe] set-icon]
if {$group_override ne ""} {
lappend cmd -group $group_override
}
#-drop-opaque-overlay: the stub contract is a payload-free prefix, so any
#non-zip trailing bytes are build detritus - concretely the COFF
#symbol/debug tables mingw-built runtimes carry after their sections,
#which ride into the extraction head. The twapi arm's EndUpdateResource
#has always silently stripped exactly those bytes (measured 2026-07-29,
#G-128 detail); punkres drops them deliberately and zeroes the dangling
#symbol-table header pointer. A REAL zip payload on a stub is unaffected
#(the flag is inert for zip-classified overlays, which punkres preserves).
lappend cmd -drop-opaque-overlay
lappend cmd [file nativename $exepath] [file nativename $icopath] 2>@1
if {[catch $cmd output]} {
error "punkres set-icon failed: $output"
}
return $ico_count
}
#Replace RT_ICON (type 3) and RT_GROUP_ICON (type 14) in the PE at exepath with
#the images of icopath. Follows sfe-0.2.tm ReplaceIconInStub generalized: all
#existing icon/group entries (any name, any lang) are deleted and the new set
#written as ids 1..N under the first pre-existing group's name+lang (fallback
#name 1, lang 1033), in one resource-update transaction. A non-empty
#group_override replaces the group NAME only (language adoption unchanged) -
#the same override punkres's 'set-icon -group' applies, so the seam's naming
#policy keeps the two arms in parity (G-128). Raises an error on failure
#(after -discard); returns the number of images embedded. The file at exepath
#must carry NO appended payload - a resource update rewrites the image.
proc ::punkboot::kit_icon_embed_twapi {exepath icopath {group_override {}}} {
set f [open $icopath rb]
set icondata [read $f]
close $f
binary scan $icondata ttt ico_reserved ico_type ico_count
if {$ico_reserved != 0 || $ico_type != 1} {
error "$icopath is not recognisable as a .ico file"
}
if {$ico_count == 0} {
error "no images in $icopath"
}
set old_icons [list]
set old_groups [list]
set libh [twapi::load_library $exepath -datafile]
try {
set resources [twapi::extract_resources $libh]
foreach {restype var} {3 old_icons 14 old_groups} {
if {[dict exists $resources $restype]} {
foreach rname [dict keys [dict get $resources $restype]] {
foreach rlang [dict keys [dict get $resources $restype $rname]] {
lappend $var [list $rname $rlang]
}
}
}
}
} trap {TWAPI_WIN32 1812} {} {
#no resource section at all - nothing to enumerate
} finally {
twapi::free_library $libh
}
set group_name 1
set group_lang 1033
if {[llength $old_groups]} {
lassign [lindex $old_groups 0] group_name group_lang
}
if {$group_override ne ""} {
set group_name $group_override
}
set update_h [twapi::begin_resource_update $exepath]
try {
foreach entry $old_icons {
lassign $entry rname rlang
twapi::delete_resource $update_h 3 $rname $rlang
}
foreach entry $old_groups {
lassign $entry rname rlang
twapi::delete_resource $update_h 14 $rname $rlang
}
set grouphdr [binary format ttt 0 1 $ico_count]
for {set i 0} {$i < $ico_count} {incr i} {
#.ico ICONDIRENTRY is 16 bytes; GRPICONDIRENTRY is 14 - the trailing
#4-byte image offset becomes a 2-byte icon resource id. Raw width and
#height bytes are carried as-is (0 means 256 in both formats).
set off [expr {6 + 16 * $i}]
binary scan $icondata "@${off} cu cu cu cu tu tu nu nu" w h cc rsv planes bits bytesinres imgoff
set iconid [expr {$i + 1}]
append grouphdr [binary format "cu cu cu cu tu tu nu tu" $w $h $cc $rsv $planes $bits $bytesinres $iconid]
twapi::update_resource $update_h 3 $iconid $group_lang [string range $icondata $imgoff [expr {$imgoff + $bytesinres - 1}]]
}
twapi::update_resource $update_h 14 $group_name $group_lang $grouphdr
} on error {msg ropts} {
catch {twapi::end_resource_update $update_h -discard}
return -options $ropts $msg
}
twapi::end_resource_update $update_h
return $ico_count
}
#G-057 single internal entry point. Decides the kit's icon (override: root
#punkshell.ico in the kit's own custom .vfs folder, pre-merge; else the project
#default src/runtime/punkshell.ico), writes/refreshes the kit's
#<kitname>.resources.toml sidecar in the build folder, and - when the target
#has PE resources and this host can write them - stamps a per-kit copy of the
#payload-free raw runtime prefix. Returns {wrap_runtime sidecarpath}:
#wrap_runtime is the runtime prefix the assembly step must consume (the stamped
#copy when embedding happened, the untouched raw prefix otherwise).
proc ::punkboot::kit_icon_process {targetkit rt_target rtname kit_type sourcefolder vfstail raw_runtime buildfolder} {
set repo_root [file dirname $sourcefolder]
set override_icon $sourcefolder/vfs/$vfstail/punkshell.ico
if {[file isfile $override_icon]} {
set icon_origin override
set icon_path $override_icon
} else {
set icon_origin default
set icon_path $sourcefolder/runtime/punkshell.ico
}
set have_icon [file isfile $icon_path]
#RT_GROUP_ICON name policy (G-128, user decision 2026-07-29): derived from
#the icon FILE's rootname, uppercased per the RC convention - punkshell.ico
#gives PUNKSHELL for the default icon and for per-kit overrides alike
#(overrides are by convention also named punkshell.ico). Applied by
#whichever mechanism embeds; language stays adopted from the replaced group.
set icon_group ""
if {$have_icon} {
set icon_group [string toupper [file rootname [file tail $icon_path]]]
}
set embedding none
set status ""
set reason ""
if {![string match "win32-*" $rt_target]} {
set status not-applicable
set reason "target $rt_target has no PE resources"
} elseif {$rtname eq "-"} {
set status not-applicable
set reason "runtimeless .kit artifact has no PE container"
} elseif {$kit_type in {cookit cookfs}} {
set status unavailable
set reason "kit type $kit_type edits the payload in place inside the mounted runtime - no payload-free stub moment to stamp"
} elseif {!$have_icon} {
set status unavailable
set reason "icon file not found at $icon_path"
} elseif {$raw_runtime in {{} -} || ![file isfile $raw_runtime]} {
set status unavailable
set reason "extraction produced no raw runtime prefix to stamp"
} else {
#mechanism selection (G-128): the portable stamper when built, else the
#twapi arm; only when neither serves does the step skip - the combined
#reason names both gaps and the punkres build remedy
lassign [::punkboot::kiticon::punkres_available $repo_root] punkres_ok punkres_info
if {!$punkres_ok} {
lassign [::punkboot::kiticon::twapi_available] twapi_ok twapi_reason
if {!$twapi_ok} {
set status unavailable
set reason "$twapi_reason; $punkres_info"
}
}
}
set wrap_runtime $raw_runtime
if {$status eq ""} {
set stamped $buildfolder/iconed_$targetkit
file copy -force $raw_runtime $stamped
catch {exec chmod +w $stamped}
if {$punkres_ok} {
set embedding punkres
set embed_cmd [list ::punkboot::kit_icon_embed_punkres $punkres_info $stamped $icon_path $icon_group]
} else {
set embedding twapi
set embed_cmd [list ::punkboot::kit_icon_embed_twapi $stamped $icon_path $icon_group]
}
if {[catch $embed_cmd embed_result]} {
::punkboot::print_build_warnings [list "kit icon embedding FAILED for $targetkit ($icon_origin icon $icon_path via $embedding): $embed_result - kit keeps its runtime's own icon"]
set status failed
set reason $embed_result
catch {file delete $stamped}
} else {
set status embedded
set wrap_runtime $stamped
puts stdout " kit icon (G-057): embedded $icon_origin icon into $targetkit stub via $embedding ($embed_result images from [file tail $icon_path])"
}
} elseif {$status eq "not-applicable"} {
puts stdout " kit icon (G-057): embedding NOT APPLICABLE for $targetkit - $reason (sidecar still written)"
} else {
puts stdout " kit icon (G-057): embedding UNAVAILABLE for $targetkit - $reason (sidecar still written)"
}
set esc ::punkboot::kiticon::toml_escape
set lines [list]
lappend lines "# kit resource record (schema 1) for the kit artifact named by this file"
lappend lines "# with the .resources.toml suffix stripped. Emitted by the make.tcl bake"
lappend lines "# icon step (G-057). Optional and advisory; format: bin/AGENTS.md."
lappend lines "schema = 1"
lappend lines "kit = \"[$esc $targetkit]\""
lappend lines "target = \"[$esc $rt_target]\""
lappend lines "kit_type = \"[$esc $kit_type]\""
lappend lines "icon_origin = \"$icon_origin\""
lappend lines "icon_source = \"[$esc [punkcheck::lib::path_relative $repo_root $icon_path]]\""
if {$have_icon} {
lappend lines "icon_hash = \"sha256:[::punkboot::kiticon::sha256_file $icon_path]\""
set ao [::punkboot::kiticon::assetorigin_read $icon_path]
if {[dict exists $ao source]} {
lappend lines "icon_provenance_source = \"[$esc [dict get $ao source]]\""
}
if {[dict exists $ao source_hash]} {
lappend lines "icon_provenance_source_hash = \"[$esc [dict get $ao source_hash]]\""
}
lappend lines "icon_group = \"[$esc $icon_group]\""
}
lappend lines "embedding = \"$embedding\""
lappend lines "embedding_status = \"$status\""
lappend lines "embedding_reason = \"[$esc $reason]\""
set sidecar $buildfolder/$targetkit.resources.toml
::punkboot::kiticon::writefile_ifchanged $sidecar "[join $lines \n]\n"
return [list $wrap_runtime $sidecar]
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set startdir [pwd]
puts stdout "Found [llength $vfs_tails] .vfs folders - checking each for executables that may need to be built"
cd [file dirname $buildfolder]
@ -6461,6 +6964,11 @@ foreach vfstail $vfs_tails {
$vfs_event targetset_addsource $sourcefolder/vfs/_config ;#some files linked via fauxlink - need to detect change
$vfs_event targetset_addsource $sourcefolder/vfs/_vfscommon.vfs
$vfs_event targetset_addsource $sourcefolder/vfs/$vfstail
if {[file isfile $sourcefolder/runtime/punkshell.ico]} {
#G-057: the default kit icon is a build input - a changed icon rebuilds
#kits (an override icon lives inside the .vfs folder, already a source)
$vfs_event targetset_addsource $sourcefolder/runtime/punkshell.ico
}
if {$rtname ne "-"} {
set building_runtime $buildfolder/build_$runtime_fullname ;#working copy of runtime executable - (possibly with kit/zipfs/cookfs etc attached!)
$vfs_event targetset_addsource $building_runtime
@ -6857,6 +7365,13 @@ foreach vfstail $vfs_tails {
continue
}
#G-057 kit icon step: sidecar for every kit; PE icon embedding where
#the target has PE resources and this host can write them. Stub-first:
#the assemblers below consume wrap_runtime - the stamped per-kit copy
#of the payload-free raw prefix when embedding happened, the untouched
#raw prefix otherwise. Runtime store originals are never modified.
lassign [::punkboot::kit_icon_process $targetkit $rt_target $rtname $target_kit_type $sourcefolder $vfstail $raw_runtime $buildfolder] wrap_runtime kiticon_sidecar
set wrapvfs $targetvfs
switch -- $target_kit_type {
zip {
@ -6886,14 +7401,14 @@ foreach vfstail $vfs_tails {
}
if {[info commands ::tcl::zipfs::mkimg] ne ""} {
#note - as at 2024-08 - there is some discussion about the interface to mkimg - it is considered unstable (may change to -option value syntax)
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $raw_runtime"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $raw_runtime
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $wrap_runtime"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $wrap_runtime
} else {
#G-122: the driving tcl has no zipfs (8.6) - assemble the same
#kind of image by concatenation. The result mounts on any
#zipfs-capable runtime (archive-start-relative offsets).
puts stderr "WARNING: tcl shell '[info nameofexecutable]' has no zipfs - assembling zip kit $targetkit by concatenation (raw runtime + mkzip)"
::punkboot::assemble_zipcat_image $raw_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
::punkboot::assemble_zipcat_image $wrap_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
}
} result ]} {
set failmsg "zipfs mkimg failed with msg: $result"
@ -6930,7 +7445,7 @@ foreach vfstail $vfs_tails {
}
#'archive' based zip offsets - editable in 7z,peazip
::punkboot::assemble_zipcat_image $raw_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
::punkboot::assemble_zipcat_image $wrap_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
} result ]} {
set failmsg "creating zipcat image failed with msg: $result"
puts stderr "creating image (zipcat) $targetkit failed"
@ -7005,13 +7520,13 @@ foreach vfstail $vfs_tails {
cd [file dirname $building_runtime] ;#make sure SDX is called with the same working dir as the files we're working on.
if {[catch {
if {$rtname ne "-"} {
exec {*}$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $raw_runtime {*}$verbose
exec {*}$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $wrap_runtime {*}$verbose
} else {
exec {*}$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs {*}$verbose
}
} result]} {
if {$rtname ne "-"} {
set sdxmsg "$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $raw_runtime {*}$verbose failed with msg: $result"
set sdxmsg "$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $wrap_runtime {*}$verbose failed with msg: $result"
} else {
set sdxmsg "$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs {*}$verbose failed with msg: $result"
}
@ -7175,6 +7690,21 @@ foreach vfstail $vfs_tails {
# -- --- --- --- --- ---
$vfs_event targetset_end OK
#G-134 post-assembly offset-style pin: the pipeline emits
#ARCHIVE-relative zip payloads by construction - this probe makes
#that a checked contract. Advisory per the G-133 posture: a
#file-relative image earns a recapped BUILD-WARNING (the G-128
#stamper would refuse it by default) and the kit still builds and
#deploys; plain/none (no attached zip - the metakit kit shape) and
#unreadable results are silence. One EOCD scan + CD walk per
#REBUILT kit only.
lassign [::punkboot::get_kit_offsetstyle_report $buildfolder/$targetkit] offprobe_available offprobe
if {!$offprobe_available} {
puts stderr "NOTE: kit offset-style pin unavailable (punkboot::utils kit_offsetstyle_report not loadable from bootsupport) - continuing without it"
} elseif {[dict get $offprobe offsetstyle] eq "file"} {
::punkboot::print_build_warnings [list "kit $targetkit assembled with a FILE-relative zip payload (G-134 pin: the pipeline emits archive-relative by construction; the G-128 stamper refuses file-relative by default) - investigate this kit's assembly path"]
}
#G-133 post-build smoke-require probe. Only an actual 'package require'
#inside the built artifact observes package RESOLUTION - the wrong-arch
#version-shadowing class is invisible to every structural check because
@ -7249,6 +7779,10 @@ foreach vfstail $vfs_tails {
puts stdout "$deployment_folder/$targetkit"
after 300
file copy $buildfolder/$targetkit $deployment_folder/$targetkit
#G-057: the kit resource sidecar ships with the kit
if {[file isfile $buildfolder/$targetkit.resources.toml]} {
file copy -force $buildfolder/$targetkit.resources.toml $deployment_folder/$targetkit.resources.toml
}
lappend installed_kits $targetkit
# -- ----------
$bin_event targetset_end OK
@ -7257,6 +7791,12 @@ foreach vfstail $vfs_tails {
set skipped_kit_install 1
puts stderr "."
puts stdout "Skipping kit install for $targetkit with vfs $vfstail runtime $rtname - no change detected"
#G-057: keep the deployed resource sidecar current even when the kit
#itself is unchanged (the record can be newer than the kit rebuild -
#e.g the sidecar feature arriving, or a host-capability change)
if {[::punkboot::kiticon::copy_ifchanged $buildfolder/$targetkit.resources.toml $deployment_folder/$targetkit.resources.toml] eq "copied"} {
puts stdout " kit icon (G-057): refreshed deployed sidecar $deployment_folder/$targetkit.resources.toml"
}
lappend skipped_kit_installs [list kit $targetkit reason "no change detected"]
$bin_event targetset_end SKIPPED
}

61
src/modules/punkboot/utils-999999.0a1.0.tm

@ -23,7 +23,7 @@ package require Tcl 8.6-
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punkboot::utils {
variable version 0.1.0
namespace export binary_arch_classify parse_punkproject_version read_punkproject_version read_changelog_latest_version vcs_dirty_warnings vfs_binary_arch_report vfs_boot_library_report
namespace export binary_arch_classify kit_offsetstyle_report parse_punkproject_version read_punkproject_version read_changelog_latest_version vcs_dirty_warnings vfs_binary_arch_report vfs_boot_library_report
namespace eval argdoc {
@ -736,6 +736,65 @@ namespace eval punkboot::utils {
dict set report ok [expr {[llength $mismatches] == 0}]
return $report
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punkboot::utils::kit_offsetstyle_report
@cmd -name "::punkboot::utils::kit_offsetstyle_report"\
-summary\
"Advisory zip offset-style probe of an assembled kit image (G-134)"\
-help\
"Probes a finished kit image file with punk::zip::archive_info and
reports the offset convention of any attached zip payload. The
punkshell bake pipeline emits ARCHIVE-relative payloads by
construction; this probe makes that a checked contract - a
'file' result is the pipeline-regression signal make.tcl
surfaces as a recapped BUILD-WARNING (advisory: the kit still
builds and deploys). 'plain' (the file is a bare zip), 'none'
(no zip attached - e.g the metakit kit shape, or any non-zip
file) and 'unreadable' are silence, not warnings: the pin
covers the offset convention of an attached zip, never the
presence of one. Purely structural: one EOCD scan plus a
central-directory walk, nothing executed, so cross-target kit
images are covered.
Returned dict keys:
offsetstyle archive | file | plain | none | unreadable
dataoffset payload base offset when archive_info supplies
one (\"\" otherwise)
detail diagnostic text for unreadable (missing file,
punk::zip unavailable, archive_info error);
empty otherwise"
@leaders
kitpath -type string -optional 0 -help\
"Path of the assembled kit image file to probe"
}]
}
proc kit_offsetstyle_report {kitpath} {
set report [dict create offsetstyle unreadable dataoffset "" detail ""]
if {![file isfile $kitpath]} {
dict set report detail "no such file: $kitpath"
return $report
}
if {[catch {package require punk::zip} errM]} {
dict set report detail "punk::zip not available: $errM"
return $report
}
if {[catch {punk::zip::archive_info $kitpath} zinfo]} {
dict set report detail "archive_info error: $zinfo"
return $report
}
if {[dict exists $zinfo offsetstyle]} {
dict set report offsetstyle [dict get $zinfo offsetstyle]
} else {
dict set report detail "archive_info returned no offsetstyle"
return $report
}
if {[dict exists $zinfo dataoffset]} {
dict set report dataoffset [dict get $zinfo dataoffset]
}
return $report
}
}
namespace eval ::punk::args::register {

3
src/modules/punkboot/utils-buildversion.txt

@ -1,6 +1,7 @@
0.5.0
0.6.0
#First line must be a semantic version number
#all other lines are ignored.
#0.6.0 - added kit_offsetstyle_report (advisory zip offset-style probe of an assembled kit image via punk::zip::archive_info; backs make.tcl's G-134 archive-relative output pin)
#0.5.0 - added binary_arch_classify (PE/ELF/Mach-O header classifier, honest unknowns), platform_expected_binary, platform_discriminated_segment and vfs_binary_arch_report (advisory payload/target binary-arch scan of a merged .vfs tree with platform-subdir exemption; backs make.tcl's G-133 payload/target consistency checks)
#0.4.0 - vfs_boot_library_report recognises a third tcl-library convention: tcl<M>.<m>/ at the vfs root, used by runtimes whose archive mounts at the executable's own path rather than //zipfs:/app (the androwish/undroidwish zipfs backport for 8.6). Such a runtime was being refused as unbootable. 'checked' gains the new location.
#0.3.0 - added vfs_boot_library_report (structural boot-precondition check of an assembled .vfs tree; backs make.tcl's G-125 unbootable-kit deploy gate)

560
src/project_layouts/vendor/punk/basic/src/make.tcl vendored

@ -2300,15 +2300,24 @@ DIAGRAM 2 - KIT ASSEMBLY DETAIL (the 'make.tcl bake' stage; incl. vfslibs phase)
(gate) --> refused: kit listed under FAILED KITS, nothing
| built, nothing deployed, bin/<kit> untouched
|
| zip image appended to the runtime exe: tcl::zipfs::mkimg,
| or (driving tcl without zipfs, e.g 8.6) raw-runtime split
| + punk::zip::mkzip + concatenation - both mount the same
| kit icon step (G-057): <kit>.resources.toml sidecar records
| the icon choice for every target (default src/runtime/
| punkshell.ico; override = punkshell.ico in the kit's own .vfs
| folder, pre-merge); win32 targets on a twapi-capable host get
| the icon stamped into a per-kit copy of the raw runtime stub
| BEFORE the payload attach (non-PE target: not applicable;
| incapable host: distinct notice - sidecar written either way)
|
| zip image appended to the (stamped) runtime exe: tcl::zipfs::
| mkimg, or (driving tcl without zipfs, e.g 8.6) raw-runtime
| split + punk::zip::mkzip + concatenation - both mount the same
v
src/_build/<kit>.exe
src/_build/<kit>.exe (+ <kit>.exe.resources.toml)
|
| smoke-require probe (host-runnable kits with declared
| packages): plain 'package require' inside the artifact [K11]
| deploy step: delete old bin/<kit>.exe, copy new one in
| (the resource sidecar deploys beside the kit)
v
bin/<kit>.exe <-- FAILS (kit listed under FAILED KITS) if a
running shell still holds the old exe [K5]
@ -3469,6 +3478,20 @@ proc ::punkboot::get_vfs_binary_arch_report {vfsfolder targetplatform} {
}
return [list 1 [::punkboot::utils::vfs_binary_arch_report $vfsfolder $targetplatform]]
}
#Availability probe + advisory zip offset-style check of an assembled kit image
#(G-134), shared by the bake loop and the 'check' command report. Same guarded-
#require treatment as the checks above: a stale bootsupport snapshot degrades
#the pin to a NOTE. Returns {available report}.
proc ::punkboot::get_kit_offsetstyle_report {kitpath} {
set available [expr {\
![catch {package require punkboot::utils}]\
&& [llength [info commands ::punkboot::utils::kit_offsetstyle_report]]\
}]
if {!$available} {
return [list 0 [dict create]]
}
return [list 1 [::punkboot::utils::kit_offsetstyle_report $kitpath]]
}
#G-133 smoke-require probe: execute a freshly built kit artifact via its 'tclsh'
#subcommand and plain-'package require' each declared package inside it. This is the
#only check that observes actual package RESOLUTION in the artifact - the punkluck86
@ -3814,6 +3837,20 @@ if {$::punkboot::command eq "check"} {
puts stdout "payload/target arch scan (G-133): UNAVAILABLE (punkboot::utils vfs_binary_arch_report not loadable from bootsupport)"
puts stdout " bake will proceed with a NOTE and cannot flag wrong-arch payload libraries - run 'make.tcl modules' then 'make.tcl bootsupport'."
}
# Kit offset-style pin (G-134) - advisory archive-relative output contract
puts stdout $sep
#availability probe only - the path deliberately does not exist
lassign [::punkboot::get_kit_offsetstyle_report [file join $projectroot __punkboot_offsetstyle_probe__]] _off_available _off_report
if {$_off_available} {
puts stdout "kit offset-style pin (G-134): ACTIVE (advisory)"
puts stdout " bake probes each assembled kit image with punk::zip::archive_info and emits a recapped"
puts stdout " BUILD-WARNING when an attached zip payload records FILE-relative offsets (the pipeline"
puts stdout " emits archive-relative by construction; the G-128 stamper refuses file-relative by default)."
puts stdout " plain/none results (no attached zip - e.g the metakit kit shape) are silence, not warnings."
} else {
puts stdout "kit offset-style pin (G-134): UNAVAILABLE (punkboot::utils kit_offsetstyle_report not loadable from bootsupport)"
puts stdout " bake will proceed with a NOTE and cannot flag a file-relative kit payload - run 'make.tcl modules' then 'make.tcl bootsupport'."
}
set _smoke_declared [list]
if {[file exists $sourcefolder/runtime/mapvfs.config]} {
set _smokemodel [punkboot::lib::mapvfs_parse $sourcefolder/runtime/mapvfs.config [file dirname $sourcefolder]/bin/runtime $sourcefolder $::punkboot::target_platform]
@ -6296,6 +6333,472 @@ proc ::punkboot::assemble_zipcat_image {raw_runtime wrapvfs outfile zipfile} {
return $zipbytes
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#G-057 kit icon step. Every built kit gets a <kitname>.resources.toml sidecar
#recording the build-time icon choice (default src/runtime/punkshell.ico,
#overridable by a root-level punkshell.ico in the kit's own custom .vfs folder,
#consulted PRE-merge so the _vfscommon copy never reads as an override), and
#win32-target kits get that icon embedded as PE RT_ICON/RT_GROUP_ICON resources
#when this host can write them. Two mechanisms share one semantic (delete ALL
#icon/group entries, write the .ico images as ids 1..N; the RT_GROUP_ICON is
#NAMED from the icon file - uppercased rootname, so punkshell.ico gives
#PUNKSHELL (user decision 2026-07-29, RC uppercase convention) - with language
#adopted from the first replaced group; idempotent by construction, parity
#recorded in the G-128 detail file): the vendored punkres portable stamper
#(G-128, src/tools/punkres, built by 'make.tcl tool build punkres') is
#selected when bin/punkres(.exe) exists and works from ANY build host;
#otherwise the twapi arm (tcl-sfe sfe-0.2.tm ReplaceIconInStub lineage; Ashok
#P. Nadkarni) serves windows hosts with nothing built. Ordering is stub-first either way: a per-kit
#copy of the payload-free raw runtime prefix is stamped BEFORE any payload is
#appended (punkres could also stamp post-hoc - the bake keeps stub-first so
#both arms stay interchangeable). kit_icon_process is the single internal
#entry point - mechanism selection lives inside it. Non-PE targets skip as NOT
#APPLICABLE; a win32 target on an incapable host skips with a distinct notice;
#both still write the sidecar.
#Decisions and skip taxonomy: goals G-057 detail file. Sidecar format doc:
#bin/AGENTS.md.
#sha256 is self-contained (same algorithm as src/assets/logo/make-ico.tcl and
#scriptlib/developer/assetorigin_check.tcl - each stays standalone: make.tcl
#must not depend on scriptlib, and this step must not depend on bootsupport
#freshness; bootsupport carries only sha1/md5). Memoized per path per run.
namespace eval ::punkboot::kiticon {
variable sha256_cache
if {![info exists sha256_cache]} {set sha256_cache [dict create]}
variable twapi_state ""
variable punkres_state ""
variable K {
0x428a2f98 0x71374491 0xb5c0fbcf 0xe9b5dba5 0x3956c25b 0x59f111f1 0x923f82a4 0xab1c5ed5
0xd807aa98 0x12835b01 0x243185be 0x550c7dc3 0x72be5d74 0x80deb1fe 0x9bdc06a7 0xc19bf174
0xe49b69c1 0xefbe4786 0x0fc19dc6 0x240ca1cc 0x2de92c6f 0x4a7484aa 0x5cb0a9dc 0x76f988da
0x983e5152 0xa831c66d 0xb00327c8 0xbf597fc7 0xc6e00bf3 0xd5a79147 0x06ca6351 0x14292967
0x27b70a85 0x2e1b2138 0x4d2c6dfc 0x53380d13 0x650a7354 0x766a0abb 0x81c2c92e 0x92722c85
0xa2bfe8a1 0xa81a664b 0xc24b8b70 0xc76c51a3 0xd192e819 0xd6990624 0xf40e3585 0x106aa070
0x19a4c116 0x1e376c08 0x2748774c 0x34b0bcb5 0x391c0cb3 0x4ed8aa4a 0x5b9cca4f 0x682e6ff3
0x748f82ee 0x78a5636f 0x84c87814 0x8cc70208 0x90befffa 0xa4506ceb 0xbef9a3f7 0xc67178f2
}
}
proc ::punkboot::kiticon::sha256_data {data} {
variable K
set bitlen [expr {wide([string length $data]) * 8}]
append data \x80
set pad [expr {(56 - [string length $data] % 64 + 64) % 64}]
append data [string repeat \x00 $pad] [binary format W $bitlen]
set h0 0x6a09e667; set h1 0xbb67ae85; set h2 0x3c6ef372; set h3 0xa54ff53a
set h4 0x510e527f; set h5 0x9b05688c; set h6 0x1f83d9ab; set h7 0x5be0cd19
set n [string length $data]
for {set off 0} {$off < $n} {incr off 64} {
binary scan [string range $data $off [expr {$off + 63}]] Iu16 w
for {set t 16} {$t < 64} {incr t} {
set x [lindex $w [expr {$t - 15}]]
set s0 [expr {((($x >> 7) | ($x << 25)) ^ (($x >> 18) | ($x << 14)) ^ ($x >> 3)) & 0xffffffff}]
set x [lindex $w [expr {$t - 2}]]
set s1 [expr {((($x >> 17) | ($x << 15)) ^ (($x >> 19) | ($x << 13)) ^ ($x >> 10)) & 0xffffffff}]
lappend w [expr {([lindex $w [expr {$t - 16}]] + $s0 + [lindex $w [expr {$t - 7}]] + $s1) & 0xffffffff}]
}
set a $h0; set b $h1; set c $h2; set d $h3
set e $h4; set f $h5; set g $h6; set h $h7
for {set t 0} {$t < 64} {incr t} {
set S1 [expr {((($e >> 6) | ($e << 26)) ^ (($e >> 11) | ($e << 21)) ^ (($e >> 25) | ($e << 7))) & 0xffffffff}]
set ch [expr {(($e & $f) ^ (~$e & $g)) & 0xffffffff}]
set T1 [expr {($h + $S1 + $ch + [lindex $K $t] + [lindex $w $t]) & 0xffffffff}]
set S0 [expr {((($a >> 2) | ($a << 30)) ^ (($a >> 13) | ($a << 19)) ^ (($a >> 22) | ($a << 10))) & 0xffffffff}]
set maj [expr {($a & $b) ^ ($a & $c) ^ ($b & $c)}]
set T2 [expr {($S0 + $maj) & 0xffffffff}]
set h $g; set g $f; set f $e; set e [expr {($d + $T1) & 0xffffffff}]
set d $c; set c $b; set b $a; set a [expr {($T1 + $T2) & 0xffffffff}]
}
set h0 [expr {($h0 + $a) & 0xffffffff}]; set h1 [expr {($h1 + $b) & 0xffffffff}]
set h2 [expr {($h2 + $c) & 0xffffffff}]; set h3 [expr {($h3 + $d) & 0xffffffff}]
set h4 [expr {($h4 + $e) & 0xffffffff}]; set h5 [expr {($h5 + $f) & 0xffffffff}]
set h6 [expr {($h6 + $g) & 0xffffffff}]; set h7 [expr {($h7 + $h) & 0xffffffff}]
}
return [format %08x%08x%08x%08x%08x%08x%08x%08x $h0 $h1 $h2 $h3 $h4 $h5 $h6 $h7]
}
proc ::punkboot::kiticon::sha256_file {path} {
variable sha256_cache
set key [file normalize $path]
if {[dict exists $sha256_cache $key]} {
return [dict get $sha256_cache $key]
}
set f [open $path rb]
set data [read $f]
close $f
set hex [sha256_data $data]
dict set sha256_cache $key $hex
return $hex
}
proc ::punkboot::kiticon::toml_escape {s} {
return [string map {\\ \\\\ \" \\\"} $s]
}
#Tolerant subset reader for the chosen icon's G-135 assetorigin sidecar - lifts
#source/source_hash so the kit record reaches back to the SVG-derived master.
#Missing sidecar, unknown schema or malformed content simply yields no
#provenance fields (never an error).
proc ::punkboot::kiticon::assetorigin_read {assetpath} {
set sc $assetpath.assetorigin.toml
set out [dict create]
if {![file isfile $sc]} {
return $out
}
if {[catch {
set f [open $sc rb]
set raw [read $f]
close $f
}]} {
return $out
}
foreach line [split [encoding convertfrom utf-8 $raw] \n] {
set line [string trim [string map [list \r {}] $line]]
if {$line eq "" || [string index $line 0] eq "#"} continue
if {[string index $line 0] eq "\["} break
set eq [string first = $line]
if {$eq < 1} continue
set key [string trim [string range $line 0 [expr {$eq - 1}]]]
if {$key ni {schema source source_hash}} continue
set val [string trim [string range $line [expr {$eq + 1}] end]]
if {[string index $val 0] eq "\""} {
set parsed ""
set ok 0
set len [string length $val]
for {set i 1} {$i < $len} {incr i} {
set chr [string index $val $i]
if {$chr eq "\\"} {
incr i
append parsed [string index $val $i]
} elseif {$chr eq "\""} {
set ok 1
break
} else {
append parsed $chr
}
}
if {!$ok} continue
set val $parsed
} else {
set cmt [string first # $val]
if {$cmt >= 0} {
set val [string trim [string range $val 0 [expr {$cmt - 1}]]]
}
}
dict set out $key $val
}
if {![dict exists $out schema] || [dict get $out schema] ne "1"} {
return [dict create]
}
return $out
}
proc ::punkboot::kiticon::writefile_ifchanged {path content} {
if {[file exists $path]} {
set f [open $path rb]
set existing [read $f]
close $f
if {$existing eq $content} {
return unchanged
}
}
set f [open $path wb]
puts -nonewline $f $content
close $f
return written
}
proc ::punkboot::kiticon::copy_ifchanged {from to} {
if {![file isfile $from]} {
return absent
}
set f [open $from rb]
set fromdata [read $f]
close $f
if {[file exists $to]} {
set f [open $to rb]
set todata [read $f]
close $f
if {$todata eq $fromdata} {
return unchanged
}
}
file copy -force $from $to
return copied
}
#Memoized once per run: can this host's tclsh write PE resources? Returns
#{1 {}} or {0 reason} with the reason distinguishing cross-host from
#twapi-unavailable (the acceptance requires distinct notices).
proc ::punkboot::kiticon::twapi_available {} {
variable twapi_state
if {$twapi_state eq "ok"} {
return [list 1 {}]
}
if {$twapi_state ne ""} {
return [list 0 $twapi_state]
}
if {[punkboot::lib::platform_process_family $::punkboot::host_platform] ne "windows"} {
set twapi_state "cross-host: win32 target baked on a $::punkboot::host_platform host cannot run the twapi resource APIs (portable post-hoc stamping is the G-128 remedy)"
return [list 0 $twapi_state]
}
if {[catch {package require twapi_resource}] && [catch {package require twapi}]} {
set twapi_state "twapi resource APIs not loadable in this tclsh ([info nameofexecutable]) - the vendored twapi under vendorlib_tcl<N>/win32-x86_64 serves native windows tclsh builds"
return [list 0 $twapi_state]
}
if {![llength [info commands ::twapi::begin_resource_update]]} {
set twapi_state "twapi loaded but its resource-update APIs are absent"
return [list 0 $twapi_state]
}
set twapi_state ok
return [list 1 {}]
}
#Memoized once per run: locate the built punkres portable stamper (G-128).
#Returns {1 <exepath>} or {0 <reason>}. Presence of the binary IS the
#mechanism-selection signal: with no tool built the twapi arm and its skip
#taxonomy behave exactly as they did before G-128 (zig stays optional).
proc ::punkboot::kiticon::punkres_available {repo_root} {
variable punkres_state
if {$punkres_state ne ""} {
return $punkres_state
}
if {[punkboot::lib::platform_process_family $::punkboot::host_platform] eq "windows"} {
set exe $repo_root/bin/punkres.exe
} else {
set exe $repo_root/bin/punkres
}
if {[file isfile $exe]} {
set punkres_state [list 1 $exe]
} else {
set punkres_state [list 0 "portable stamper not present at $exe - 'tclsh src/make.tcl tool build punkres' builds it from vendored source, or fetch the prebuilt punkbin artifact (win32-x86_64/tools/punkres-*; route: src/tools/punkres/PROVENANCE.md) (G-128)"]
}
return $punkres_state
}
#G-128 punkres arm: exec the vendored portable stamper on the payload-free stub
#copy. punkres itself deletes all icon/group entries, writes the images as ids
#1..N under the first pre-existing group's name+lang (a non-empty
#group_override renames the group via 'set-icon -group' - the seam's naming
#policy; language and codepage adoption unchanged), and re-reads its own
#output (resources AND any overlay) before reporting success - equivalent
#resource content to the twapi arm from the same input (parity evidence in the
#G-128 detail file). Returns the number of images embedded; raises an error
#carrying punkres's output on any nonzero exit.
proc ::punkboot::kit_icon_embed_punkres {punkres_exe exepath icopath {group_override {}}} {
set f [open $icopath rb]
set icondata [read $f]
close $f
binary scan $icondata ttt ico_reserved ico_type ico_count
if {$ico_reserved != 0 || $ico_type != 1} {
error "$icopath is not recognisable as a .ico file"
}
if {$ico_count == 0} {
error "no images in $icopath"
}
set cmd [list exec [file nativename $punkres_exe] set-icon]
if {$group_override ne ""} {
lappend cmd -group $group_override
}
#-drop-opaque-overlay: the stub contract is a payload-free prefix, so any
#non-zip trailing bytes are build detritus - concretely the COFF
#symbol/debug tables mingw-built runtimes carry after their sections,
#which ride into the extraction head. The twapi arm's EndUpdateResource
#has always silently stripped exactly those bytes (measured 2026-07-29,
#G-128 detail); punkres drops them deliberately and zeroes the dangling
#symbol-table header pointer. A REAL zip payload on a stub is unaffected
#(the flag is inert for zip-classified overlays, which punkres preserves).
lappend cmd -drop-opaque-overlay
lappend cmd [file nativename $exepath] [file nativename $icopath] 2>@1
if {[catch $cmd output]} {
error "punkres set-icon failed: $output"
}
return $ico_count
}
#Replace RT_ICON (type 3) and RT_GROUP_ICON (type 14) in the PE at exepath with
#the images of icopath. Follows sfe-0.2.tm ReplaceIconInStub generalized: all
#existing icon/group entries (any name, any lang) are deleted and the new set
#written as ids 1..N under the first pre-existing group's name+lang (fallback
#name 1, lang 1033), in one resource-update transaction. A non-empty
#group_override replaces the group NAME only (language adoption unchanged) -
#the same override punkres's 'set-icon -group' applies, so the seam's naming
#policy keeps the two arms in parity (G-128). Raises an error on failure
#(after -discard); returns the number of images embedded. The file at exepath
#must carry NO appended payload - a resource update rewrites the image.
proc ::punkboot::kit_icon_embed_twapi {exepath icopath {group_override {}}} {
set f [open $icopath rb]
set icondata [read $f]
close $f
binary scan $icondata ttt ico_reserved ico_type ico_count
if {$ico_reserved != 0 || $ico_type != 1} {
error "$icopath is not recognisable as a .ico file"
}
if {$ico_count == 0} {
error "no images in $icopath"
}
set old_icons [list]
set old_groups [list]
set libh [twapi::load_library $exepath -datafile]
try {
set resources [twapi::extract_resources $libh]
foreach {restype var} {3 old_icons 14 old_groups} {
if {[dict exists $resources $restype]} {
foreach rname [dict keys [dict get $resources $restype]] {
foreach rlang [dict keys [dict get $resources $restype $rname]] {
lappend $var [list $rname $rlang]
}
}
}
}
} trap {TWAPI_WIN32 1812} {} {
#no resource section at all - nothing to enumerate
} finally {
twapi::free_library $libh
}
set group_name 1
set group_lang 1033
if {[llength $old_groups]} {
lassign [lindex $old_groups 0] group_name group_lang
}
if {$group_override ne ""} {
set group_name $group_override
}
set update_h [twapi::begin_resource_update $exepath]
try {
foreach entry $old_icons {
lassign $entry rname rlang
twapi::delete_resource $update_h 3 $rname $rlang
}
foreach entry $old_groups {
lassign $entry rname rlang
twapi::delete_resource $update_h 14 $rname $rlang
}
set grouphdr [binary format ttt 0 1 $ico_count]
for {set i 0} {$i < $ico_count} {incr i} {
#.ico ICONDIRENTRY is 16 bytes; GRPICONDIRENTRY is 14 - the trailing
#4-byte image offset becomes a 2-byte icon resource id. Raw width and
#height bytes are carried as-is (0 means 256 in both formats).
set off [expr {6 + 16 * $i}]
binary scan $icondata "@${off} cu cu cu cu tu tu nu nu" w h cc rsv planes bits bytesinres imgoff
set iconid [expr {$i + 1}]
append grouphdr [binary format "cu cu cu cu tu tu nu tu" $w $h $cc $rsv $planes $bits $bytesinres $iconid]
twapi::update_resource $update_h 3 $iconid $group_lang [string range $icondata $imgoff [expr {$imgoff + $bytesinres - 1}]]
}
twapi::update_resource $update_h 14 $group_name $group_lang $grouphdr
} on error {msg ropts} {
catch {twapi::end_resource_update $update_h -discard}
return -options $ropts $msg
}
twapi::end_resource_update $update_h
return $ico_count
}
#G-057 single internal entry point. Decides the kit's icon (override: root
#punkshell.ico in the kit's own custom .vfs folder, pre-merge; else the project
#default src/runtime/punkshell.ico), writes/refreshes the kit's
#<kitname>.resources.toml sidecar in the build folder, and - when the target
#has PE resources and this host can write them - stamps a per-kit copy of the
#payload-free raw runtime prefix. Returns {wrap_runtime sidecarpath}:
#wrap_runtime is the runtime prefix the assembly step must consume (the stamped
#copy when embedding happened, the untouched raw prefix otherwise).
proc ::punkboot::kit_icon_process {targetkit rt_target rtname kit_type sourcefolder vfstail raw_runtime buildfolder} {
set repo_root [file dirname $sourcefolder]
set override_icon $sourcefolder/vfs/$vfstail/punkshell.ico
if {[file isfile $override_icon]} {
set icon_origin override
set icon_path $override_icon
} else {
set icon_origin default
set icon_path $sourcefolder/runtime/punkshell.ico
}
set have_icon [file isfile $icon_path]
#RT_GROUP_ICON name policy (G-128, user decision 2026-07-29): derived from
#the icon FILE's rootname, uppercased per the RC convention - punkshell.ico
#gives PUNKSHELL for the default icon and for per-kit overrides alike
#(overrides are by convention also named punkshell.ico). Applied by
#whichever mechanism embeds; language stays adopted from the replaced group.
set icon_group ""
if {$have_icon} {
set icon_group [string toupper [file rootname [file tail $icon_path]]]
}
set embedding none
set status ""
set reason ""
if {![string match "win32-*" $rt_target]} {
set status not-applicable
set reason "target $rt_target has no PE resources"
} elseif {$rtname eq "-"} {
set status not-applicable
set reason "runtimeless .kit artifact has no PE container"
} elseif {$kit_type in {cookit cookfs}} {
set status unavailable
set reason "kit type $kit_type edits the payload in place inside the mounted runtime - no payload-free stub moment to stamp"
} elseif {!$have_icon} {
set status unavailable
set reason "icon file not found at $icon_path"
} elseif {$raw_runtime in {{} -} || ![file isfile $raw_runtime]} {
set status unavailable
set reason "extraction produced no raw runtime prefix to stamp"
} else {
#mechanism selection (G-128): the portable stamper when built, else the
#twapi arm; only when neither serves does the step skip - the combined
#reason names both gaps and the punkres build remedy
lassign [::punkboot::kiticon::punkres_available $repo_root] punkres_ok punkres_info
if {!$punkres_ok} {
lassign [::punkboot::kiticon::twapi_available] twapi_ok twapi_reason
if {!$twapi_ok} {
set status unavailable
set reason "$twapi_reason; $punkres_info"
}
}
}
set wrap_runtime $raw_runtime
if {$status eq ""} {
set stamped $buildfolder/iconed_$targetkit
file copy -force $raw_runtime $stamped
catch {exec chmod +w $stamped}
if {$punkres_ok} {
set embedding punkres
set embed_cmd [list ::punkboot::kit_icon_embed_punkres $punkres_info $stamped $icon_path $icon_group]
} else {
set embedding twapi
set embed_cmd [list ::punkboot::kit_icon_embed_twapi $stamped $icon_path $icon_group]
}
if {[catch $embed_cmd embed_result]} {
::punkboot::print_build_warnings [list "kit icon embedding FAILED for $targetkit ($icon_origin icon $icon_path via $embedding): $embed_result - kit keeps its runtime's own icon"]
set status failed
set reason $embed_result
catch {file delete $stamped}
} else {
set status embedded
set wrap_runtime $stamped
puts stdout " kit icon (G-057): embedded $icon_origin icon into $targetkit stub via $embedding ($embed_result images from [file tail $icon_path])"
}
} elseif {$status eq "not-applicable"} {
puts stdout " kit icon (G-057): embedding NOT APPLICABLE for $targetkit - $reason (sidecar still written)"
} else {
puts stdout " kit icon (G-057): embedding UNAVAILABLE for $targetkit - $reason (sidecar still written)"
}
set esc ::punkboot::kiticon::toml_escape
set lines [list]
lappend lines "# kit resource record (schema 1) for the kit artifact named by this file"
lappend lines "# with the .resources.toml suffix stripped. Emitted by the make.tcl bake"
lappend lines "# icon step (G-057). Optional and advisory; format: bin/AGENTS.md."
lappend lines "schema = 1"
lappend lines "kit = \"[$esc $targetkit]\""
lappend lines "target = \"[$esc $rt_target]\""
lappend lines "kit_type = \"[$esc $kit_type]\""
lappend lines "icon_origin = \"$icon_origin\""
lappend lines "icon_source = \"[$esc [punkcheck::lib::path_relative $repo_root $icon_path]]\""
if {$have_icon} {
lappend lines "icon_hash = \"sha256:[::punkboot::kiticon::sha256_file $icon_path]\""
set ao [::punkboot::kiticon::assetorigin_read $icon_path]
if {[dict exists $ao source]} {
lappend lines "icon_provenance_source = \"[$esc [dict get $ao source]]\""
}
if {[dict exists $ao source_hash]} {
lappend lines "icon_provenance_source_hash = \"[$esc [dict get $ao source_hash]]\""
}
lappend lines "icon_group = \"[$esc $icon_group]\""
}
lappend lines "embedding = \"$embedding\""
lappend lines "embedding_status = \"$status\""
lappend lines "embedding_reason = \"[$esc $reason]\""
set sidecar $buildfolder/$targetkit.resources.toml
::punkboot::kiticon::writefile_ifchanged $sidecar "[join $lines \n]\n"
return [list $wrap_runtime $sidecar]
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set startdir [pwd]
puts stdout "Found [llength $vfs_tails] .vfs folders - checking each for executables that may need to be built"
cd [file dirname $buildfolder]
@ -6461,6 +6964,11 @@ foreach vfstail $vfs_tails {
$vfs_event targetset_addsource $sourcefolder/vfs/_config ;#some files linked via fauxlink - need to detect change
$vfs_event targetset_addsource $sourcefolder/vfs/_vfscommon.vfs
$vfs_event targetset_addsource $sourcefolder/vfs/$vfstail
if {[file isfile $sourcefolder/runtime/punkshell.ico]} {
#G-057: the default kit icon is a build input - a changed icon rebuilds
#kits (an override icon lives inside the .vfs folder, already a source)
$vfs_event targetset_addsource $sourcefolder/runtime/punkshell.ico
}
if {$rtname ne "-"} {
set building_runtime $buildfolder/build_$runtime_fullname ;#working copy of runtime executable - (possibly with kit/zipfs/cookfs etc attached!)
$vfs_event targetset_addsource $building_runtime
@ -6857,6 +7365,13 @@ foreach vfstail $vfs_tails {
continue
}
#G-057 kit icon step: sidecar for every kit; PE icon embedding where
#the target has PE resources and this host can write them. Stub-first:
#the assemblers below consume wrap_runtime - the stamped per-kit copy
#of the payload-free raw prefix when embedding happened, the untouched
#raw prefix otherwise. Runtime store originals are never modified.
lassign [::punkboot::kit_icon_process $targetkit $rt_target $rtname $target_kit_type $sourcefolder $vfstail $raw_runtime $buildfolder] wrap_runtime kiticon_sidecar
set wrapvfs $targetvfs
switch -- $target_kit_type {
zip {
@ -6886,14 +7401,14 @@ foreach vfstail $vfs_tails {
}
if {[info commands ::tcl::zipfs::mkimg] ne ""} {
#note - as at 2024-08 - there is some discussion about the interface to mkimg - it is considered unstable (may change to -option value syntax)
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $raw_runtime"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $raw_runtime
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $wrap_runtime"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $wrap_runtime
} else {
#G-122: the driving tcl has no zipfs (8.6) - assemble the same
#kind of image by concatenation. The result mounts on any
#zipfs-capable runtime (archive-start-relative offsets).
puts stderr "WARNING: tcl shell '[info nameofexecutable]' has no zipfs - assembling zip kit $targetkit by concatenation (raw runtime + mkzip)"
::punkboot::assemble_zipcat_image $raw_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
::punkboot::assemble_zipcat_image $wrap_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
}
} result ]} {
set failmsg "zipfs mkimg failed with msg: $result"
@ -6930,7 +7445,7 @@ foreach vfstail $vfs_tails {
}
#'archive' based zip offsets - editable in 7z,peazip
::punkboot::assemble_zipcat_image $raw_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
::punkboot::assemble_zipcat_image $wrap_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
} result ]} {
set failmsg "creating zipcat image failed with msg: $result"
puts stderr "creating image (zipcat) $targetkit failed"
@ -7005,13 +7520,13 @@ foreach vfstail $vfs_tails {
cd [file dirname $building_runtime] ;#make sure SDX is called with the same working dir as the files we're working on.
if {[catch {
if {$rtname ne "-"} {
exec {*}$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $raw_runtime {*}$verbose
exec {*}$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $wrap_runtime {*}$verbose
} else {
exec {*}$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs {*}$verbose
}
} result]} {
if {$rtname ne "-"} {
set sdxmsg "$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $raw_runtime {*}$verbose failed with msg: $result"
set sdxmsg "$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $wrap_runtime {*}$verbose failed with msg: $result"
} else {
set sdxmsg "$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs {*}$verbose failed with msg: $result"
}
@ -7175,6 +7690,21 @@ foreach vfstail $vfs_tails {
# -- --- --- --- --- ---
$vfs_event targetset_end OK
#G-134 post-assembly offset-style pin: the pipeline emits
#ARCHIVE-relative zip payloads by construction - this probe makes
#that a checked contract. Advisory per the G-133 posture: a
#file-relative image earns a recapped BUILD-WARNING (the G-128
#stamper would refuse it by default) and the kit still builds and
#deploys; plain/none (no attached zip - the metakit kit shape) and
#unreadable results are silence. One EOCD scan + CD walk per
#REBUILT kit only.
lassign [::punkboot::get_kit_offsetstyle_report $buildfolder/$targetkit] offprobe_available offprobe
if {!$offprobe_available} {
puts stderr "NOTE: kit offset-style pin unavailable (punkboot::utils kit_offsetstyle_report not loadable from bootsupport) - continuing without it"
} elseif {[dict get $offprobe offsetstyle] eq "file"} {
::punkboot::print_build_warnings [list "kit $targetkit assembled with a FILE-relative zip payload (G-134 pin: the pipeline emits archive-relative by construction; the G-128 stamper refuses file-relative by default) - investigate this kit's assembly path"]
}
#G-133 post-build smoke-require probe. Only an actual 'package require'
#inside the built artifact observes package RESOLUTION - the wrong-arch
#version-shadowing class is invisible to every structural check because
@ -7249,6 +7779,10 @@ foreach vfstail $vfs_tails {
puts stdout "$deployment_folder/$targetkit"
after 300
file copy $buildfolder/$targetkit $deployment_folder/$targetkit
#G-057: the kit resource sidecar ships with the kit
if {[file isfile $buildfolder/$targetkit.resources.toml]} {
file copy -force $buildfolder/$targetkit.resources.toml $deployment_folder/$targetkit.resources.toml
}
lappend installed_kits $targetkit
# -- ----------
$bin_event targetset_end OK
@ -7257,6 +7791,12 @@ foreach vfstail $vfs_tails {
set skipped_kit_install 1
puts stderr "."
puts stdout "Skipping kit install for $targetkit with vfs $vfstail runtime $rtname - no change detected"
#G-057: keep the deployed resource sidecar current even when the kit
#itself is unchanged (the record can be newer than the kit rebuild -
#e.g the sidecar feature arriving, or a host-capability change)
if {[::punkboot::kiticon::copy_ifchanged $buildfolder/$targetkit.resources.toml $deployment_folder/$targetkit.resources.toml] eq "copied"} {
puts stdout " kit icon (G-057): refreshed deployed sidecar $deployment_folder/$targetkit.resources.toml"
}
lappend skipped_kit_installs [list kit $targetkit reason "no change detected"]
$bin_event targetset_end SKIPPED
}

560
src/project_layouts/vendor/punk/project-0.1/src/make.tcl vendored

@ -2300,15 +2300,24 @@ DIAGRAM 2 - KIT ASSEMBLY DETAIL (the 'make.tcl bake' stage; incl. vfslibs phase)
(gate) --> refused: kit listed under FAILED KITS, nothing
| built, nothing deployed, bin/<kit> untouched
|
| zip image appended to the runtime exe: tcl::zipfs::mkimg,
| or (driving tcl without zipfs, e.g 8.6) raw-runtime split
| + punk::zip::mkzip + concatenation - both mount the same
| kit icon step (G-057): <kit>.resources.toml sidecar records
| the icon choice for every target (default src/runtime/
| punkshell.ico; override = punkshell.ico in the kit's own .vfs
| folder, pre-merge); win32 targets on a twapi-capable host get
| the icon stamped into a per-kit copy of the raw runtime stub
| BEFORE the payload attach (non-PE target: not applicable;
| incapable host: distinct notice - sidecar written either way)
|
| zip image appended to the (stamped) runtime exe: tcl::zipfs::
| mkimg, or (driving tcl without zipfs, e.g 8.6) raw-runtime
| split + punk::zip::mkzip + concatenation - both mount the same
v
src/_build/<kit>.exe
src/_build/<kit>.exe (+ <kit>.exe.resources.toml)
|
| smoke-require probe (host-runnable kits with declared
| packages): plain 'package require' inside the artifact [K11]
| deploy step: delete old bin/<kit>.exe, copy new one in
| (the resource sidecar deploys beside the kit)
v
bin/<kit>.exe <-- FAILS (kit listed under FAILED KITS) if a
running shell still holds the old exe [K5]
@ -3469,6 +3478,20 @@ proc ::punkboot::get_vfs_binary_arch_report {vfsfolder targetplatform} {
}
return [list 1 [::punkboot::utils::vfs_binary_arch_report $vfsfolder $targetplatform]]
}
#Availability probe + advisory zip offset-style check of an assembled kit image
#(G-134), shared by the bake loop and the 'check' command report. Same guarded-
#require treatment as the checks above: a stale bootsupport snapshot degrades
#the pin to a NOTE. Returns {available report}.
proc ::punkboot::get_kit_offsetstyle_report {kitpath} {
set available [expr {\
![catch {package require punkboot::utils}]\
&& [llength [info commands ::punkboot::utils::kit_offsetstyle_report]]\
}]
if {!$available} {
return [list 0 [dict create]]
}
return [list 1 [::punkboot::utils::kit_offsetstyle_report $kitpath]]
}
#G-133 smoke-require probe: execute a freshly built kit artifact via its 'tclsh'
#subcommand and plain-'package require' each declared package inside it. This is the
#only check that observes actual package RESOLUTION in the artifact - the punkluck86
@ -3814,6 +3837,20 @@ if {$::punkboot::command eq "check"} {
puts stdout "payload/target arch scan (G-133): UNAVAILABLE (punkboot::utils vfs_binary_arch_report not loadable from bootsupport)"
puts stdout " bake will proceed with a NOTE and cannot flag wrong-arch payload libraries - run 'make.tcl modules' then 'make.tcl bootsupport'."
}
# Kit offset-style pin (G-134) - advisory archive-relative output contract
puts stdout $sep
#availability probe only - the path deliberately does not exist
lassign [::punkboot::get_kit_offsetstyle_report [file join $projectroot __punkboot_offsetstyle_probe__]] _off_available _off_report
if {$_off_available} {
puts stdout "kit offset-style pin (G-134): ACTIVE (advisory)"
puts stdout " bake probes each assembled kit image with punk::zip::archive_info and emits a recapped"
puts stdout " BUILD-WARNING when an attached zip payload records FILE-relative offsets (the pipeline"
puts stdout " emits archive-relative by construction; the G-128 stamper refuses file-relative by default)."
puts stdout " plain/none results (no attached zip - e.g the metakit kit shape) are silence, not warnings."
} else {
puts stdout "kit offset-style pin (G-134): UNAVAILABLE (punkboot::utils kit_offsetstyle_report not loadable from bootsupport)"
puts stdout " bake will proceed with a NOTE and cannot flag a file-relative kit payload - run 'make.tcl modules' then 'make.tcl bootsupport'."
}
set _smoke_declared [list]
if {[file exists $sourcefolder/runtime/mapvfs.config]} {
set _smokemodel [punkboot::lib::mapvfs_parse $sourcefolder/runtime/mapvfs.config [file dirname $sourcefolder]/bin/runtime $sourcefolder $::punkboot::target_platform]
@ -6296,6 +6333,472 @@ proc ::punkboot::assemble_zipcat_image {raw_runtime wrapvfs outfile zipfile} {
return $zipbytes
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#G-057 kit icon step. Every built kit gets a <kitname>.resources.toml sidecar
#recording the build-time icon choice (default src/runtime/punkshell.ico,
#overridable by a root-level punkshell.ico in the kit's own custom .vfs folder,
#consulted PRE-merge so the _vfscommon copy never reads as an override), and
#win32-target kits get that icon embedded as PE RT_ICON/RT_GROUP_ICON resources
#when this host can write them. Two mechanisms share one semantic (delete ALL
#icon/group entries, write the .ico images as ids 1..N; the RT_GROUP_ICON is
#NAMED from the icon file - uppercased rootname, so punkshell.ico gives
#PUNKSHELL (user decision 2026-07-29, RC uppercase convention) - with language
#adopted from the first replaced group; idempotent by construction, parity
#recorded in the G-128 detail file): the vendored punkres portable stamper
#(G-128, src/tools/punkres, built by 'make.tcl tool build punkres') is
#selected when bin/punkres(.exe) exists and works from ANY build host;
#otherwise the twapi arm (tcl-sfe sfe-0.2.tm ReplaceIconInStub lineage; Ashok
#P. Nadkarni) serves windows hosts with nothing built. Ordering is stub-first either way: a per-kit
#copy of the payload-free raw runtime prefix is stamped BEFORE any payload is
#appended (punkres could also stamp post-hoc - the bake keeps stub-first so
#both arms stay interchangeable). kit_icon_process is the single internal
#entry point - mechanism selection lives inside it. Non-PE targets skip as NOT
#APPLICABLE; a win32 target on an incapable host skips with a distinct notice;
#both still write the sidecar.
#Decisions and skip taxonomy: goals G-057 detail file. Sidecar format doc:
#bin/AGENTS.md.
#sha256 is self-contained (same algorithm as src/assets/logo/make-ico.tcl and
#scriptlib/developer/assetorigin_check.tcl - each stays standalone: make.tcl
#must not depend on scriptlib, and this step must not depend on bootsupport
#freshness; bootsupport carries only sha1/md5). Memoized per path per run.
namespace eval ::punkboot::kiticon {
variable sha256_cache
if {![info exists sha256_cache]} {set sha256_cache [dict create]}
variable twapi_state ""
variable punkres_state ""
variable K {
0x428a2f98 0x71374491 0xb5c0fbcf 0xe9b5dba5 0x3956c25b 0x59f111f1 0x923f82a4 0xab1c5ed5
0xd807aa98 0x12835b01 0x243185be 0x550c7dc3 0x72be5d74 0x80deb1fe 0x9bdc06a7 0xc19bf174
0xe49b69c1 0xefbe4786 0x0fc19dc6 0x240ca1cc 0x2de92c6f 0x4a7484aa 0x5cb0a9dc 0x76f988da
0x983e5152 0xa831c66d 0xb00327c8 0xbf597fc7 0xc6e00bf3 0xd5a79147 0x06ca6351 0x14292967
0x27b70a85 0x2e1b2138 0x4d2c6dfc 0x53380d13 0x650a7354 0x766a0abb 0x81c2c92e 0x92722c85
0xa2bfe8a1 0xa81a664b 0xc24b8b70 0xc76c51a3 0xd192e819 0xd6990624 0xf40e3585 0x106aa070
0x19a4c116 0x1e376c08 0x2748774c 0x34b0bcb5 0x391c0cb3 0x4ed8aa4a 0x5b9cca4f 0x682e6ff3
0x748f82ee 0x78a5636f 0x84c87814 0x8cc70208 0x90befffa 0xa4506ceb 0xbef9a3f7 0xc67178f2
}
}
proc ::punkboot::kiticon::sha256_data {data} {
variable K
set bitlen [expr {wide([string length $data]) * 8}]
append data \x80
set pad [expr {(56 - [string length $data] % 64 + 64) % 64}]
append data [string repeat \x00 $pad] [binary format W $bitlen]
set h0 0x6a09e667; set h1 0xbb67ae85; set h2 0x3c6ef372; set h3 0xa54ff53a
set h4 0x510e527f; set h5 0x9b05688c; set h6 0x1f83d9ab; set h7 0x5be0cd19
set n [string length $data]
for {set off 0} {$off < $n} {incr off 64} {
binary scan [string range $data $off [expr {$off + 63}]] Iu16 w
for {set t 16} {$t < 64} {incr t} {
set x [lindex $w [expr {$t - 15}]]
set s0 [expr {((($x >> 7) | ($x << 25)) ^ (($x >> 18) | ($x << 14)) ^ ($x >> 3)) & 0xffffffff}]
set x [lindex $w [expr {$t - 2}]]
set s1 [expr {((($x >> 17) | ($x << 15)) ^ (($x >> 19) | ($x << 13)) ^ ($x >> 10)) & 0xffffffff}]
lappend w [expr {([lindex $w [expr {$t - 16}]] + $s0 + [lindex $w [expr {$t - 7}]] + $s1) & 0xffffffff}]
}
set a $h0; set b $h1; set c $h2; set d $h3
set e $h4; set f $h5; set g $h6; set h $h7
for {set t 0} {$t < 64} {incr t} {
set S1 [expr {((($e >> 6) | ($e << 26)) ^ (($e >> 11) | ($e << 21)) ^ (($e >> 25) | ($e << 7))) & 0xffffffff}]
set ch [expr {(($e & $f) ^ (~$e & $g)) & 0xffffffff}]
set T1 [expr {($h + $S1 + $ch + [lindex $K $t] + [lindex $w $t]) & 0xffffffff}]
set S0 [expr {((($a >> 2) | ($a << 30)) ^ (($a >> 13) | ($a << 19)) ^ (($a >> 22) | ($a << 10))) & 0xffffffff}]
set maj [expr {($a & $b) ^ ($a & $c) ^ ($b & $c)}]
set T2 [expr {($S0 + $maj) & 0xffffffff}]
set h $g; set g $f; set f $e; set e [expr {($d + $T1) & 0xffffffff}]
set d $c; set c $b; set b $a; set a [expr {($T1 + $T2) & 0xffffffff}]
}
set h0 [expr {($h0 + $a) & 0xffffffff}]; set h1 [expr {($h1 + $b) & 0xffffffff}]
set h2 [expr {($h2 + $c) & 0xffffffff}]; set h3 [expr {($h3 + $d) & 0xffffffff}]
set h4 [expr {($h4 + $e) & 0xffffffff}]; set h5 [expr {($h5 + $f) & 0xffffffff}]
set h6 [expr {($h6 + $g) & 0xffffffff}]; set h7 [expr {($h7 + $h) & 0xffffffff}]
}
return [format %08x%08x%08x%08x%08x%08x%08x%08x $h0 $h1 $h2 $h3 $h4 $h5 $h6 $h7]
}
proc ::punkboot::kiticon::sha256_file {path} {
variable sha256_cache
set key [file normalize $path]
if {[dict exists $sha256_cache $key]} {
return [dict get $sha256_cache $key]
}
set f [open $path rb]
set data [read $f]
close $f
set hex [sha256_data $data]
dict set sha256_cache $key $hex
return $hex
}
proc ::punkboot::kiticon::toml_escape {s} {
return [string map {\\ \\\\ \" \\\"} $s]
}
#Tolerant subset reader for the chosen icon's G-135 assetorigin sidecar - lifts
#source/source_hash so the kit record reaches back to the SVG-derived master.
#Missing sidecar, unknown schema or malformed content simply yields no
#provenance fields (never an error).
proc ::punkboot::kiticon::assetorigin_read {assetpath} {
set sc $assetpath.assetorigin.toml
set out [dict create]
if {![file isfile $sc]} {
return $out
}
if {[catch {
set f [open $sc rb]
set raw [read $f]
close $f
}]} {
return $out
}
foreach line [split [encoding convertfrom utf-8 $raw] \n] {
set line [string trim [string map [list \r {}] $line]]
if {$line eq "" || [string index $line 0] eq "#"} continue
if {[string index $line 0] eq "\["} break
set eq [string first = $line]
if {$eq < 1} continue
set key [string trim [string range $line 0 [expr {$eq - 1}]]]
if {$key ni {schema source source_hash}} continue
set val [string trim [string range $line [expr {$eq + 1}] end]]
if {[string index $val 0] eq "\""} {
set parsed ""
set ok 0
set len [string length $val]
for {set i 1} {$i < $len} {incr i} {
set chr [string index $val $i]
if {$chr eq "\\"} {
incr i
append parsed [string index $val $i]
} elseif {$chr eq "\""} {
set ok 1
break
} else {
append parsed $chr
}
}
if {!$ok} continue
set val $parsed
} else {
set cmt [string first # $val]
if {$cmt >= 0} {
set val [string trim [string range $val 0 [expr {$cmt - 1}]]]
}
}
dict set out $key $val
}
if {![dict exists $out schema] || [dict get $out schema] ne "1"} {
return [dict create]
}
return $out
}
proc ::punkboot::kiticon::writefile_ifchanged {path content} {
if {[file exists $path]} {
set f [open $path rb]
set existing [read $f]
close $f
if {$existing eq $content} {
return unchanged
}
}
set f [open $path wb]
puts -nonewline $f $content
close $f
return written
}
proc ::punkboot::kiticon::copy_ifchanged {from to} {
if {![file isfile $from]} {
return absent
}
set f [open $from rb]
set fromdata [read $f]
close $f
if {[file exists $to]} {
set f [open $to rb]
set todata [read $f]
close $f
if {$todata eq $fromdata} {
return unchanged
}
}
file copy -force $from $to
return copied
}
#Memoized once per run: can this host's tclsh write PE resources? Returns
#{1 {}} or {0 reason} with the reason distinguishing cross-host from
#twapi-unavailable (the acceptance requires distinct notices).
proc ::punkboot::kiticon::twapi_available {} {
variable twapi_state
if {$twapi_state eq "ok"} {
return [list 1 {}]
}
if {$twapi_state ne ""} {
return [list 0 $twapi_state]
}
if {[punkboot::lib::platform_process_family $::punkboot::host_platform] ne "windows"} {
set twapi_state "cross-host: win32 target baked on a $::punkboot::host_platform host cannot run the twapi resource APIs (portable post-hoc stamping is the G-128 remedy)"
return [list 0 $twapi_state]
}
if {[catch {package require twapi_resource}] && [catch {package require twapi}]} {
set twapi_state "twapi resource APIs not loadable in this tclsh ([info nameofexecutable]) - the vendored twapi under vendorlib_tcl<N>/win32-x86_64 serves native windows tclsh builds"
return [list 0 $twapi_state]
}
if {![llength [info commands ::twapi::begin_resource_update]]} {
set twapi_state "twapi loaded but its resource-update APIs are absent"
return [list 0 $twapi_state]
}
set twapi_state ok
return [list 1 {}]
}
#Memoized once per run: locate the built punkres portable stamper (G-128).
#Returns {1 <exepath>} or {0 <reason>}. Presence of the binary IS the
#mechanism-selection signal: with no tool built the twapi arm and its skip
#taxonomy behave exactly as they did before G-128 (zig stays optional).
proc ::punkboot::kiticon::punkres_available {repo_root} {
variable punkres_state
if {$punkres_state ne ""} {
return $punkres_state
}
if {[punkboot::lib::platform_process_family $::punkboot::host_platform] eq "windows"} {
set exe $repo_root/bin/punkres.exe
} else {
set exe $repo_root/bin/punkres
}
if {[file isfile $exe]} {
set punkres_state [list 1 $exe]
} else {
set punkres_state [list 0 "portable stamper not present at $exe - 'tclsh src/make.tcl tool build punkres' builds it from vendored source, or fetch the prebuilt punkbin artifact (win32-x86_64/tools/punkres-*; route: src/tools/punkres/PROVENANCE.md) (G-128)"]
}
return $punkres_state
}
#G-128 punkres arm: exec the vendored portable stamper on the payload-free stub
#copy. punkres itself deletes all icon/group entries, writes the images as ids
#1..N under the first pre-existing group's name+lang (a non-empty
#group_override renames the group via 'set-icon -group' - the seam's naming
#policy; language and codepage adoption unchanged), and re-reads its own
#output (resources AND any overlay) before reporting success - equivalent
#resource content to the twapi arm from the same input (parity evidence in the
#G-128 detail file). Returns the number of images embedded; raises an error
#carrying punkres's output on any nonzero exit.
proc ::punkboot::kit_icon_embed_punkres {punkres_exe exepath icopath {group_override {}}} {
set f [open $icopath rb]
set icondata [read $f]
close $f
binary scan $icondata ttt ico_reserved ico_type ico_count
if {$ico_reserved != 0 || $ico_type != 1} {
error "$icopath is not recognisable as a .ico file"
}
if {$ico_count == 0} {
error "no images in $icopath"
}
set cmd [list exec [file nativename $punkres_exe] set-icon]
if {$group_override ne ""} {
lappend cmd -group $group_override
}
#-drop-opaque-overlay: the stub contract is a payload-free prefix, so any
#non-zip trailing bytes are build detritus - concretely the COFF
#symbol/debug tables mingw-built runtimes carry after their sections,
#which ride into the extraction head. The twapi arm's EndUpdateResource
#has always silently stripped exactly those bytes (measured 2026-07-29,
#G-128 detail); punkres drops them deliberately and zeroes the dangling
#symbol-table header pointer. A REAL zip payload on a stub is unaffected
#(the flag is inert for zip-classified overlays, which punkres preserves).
lappend cmd -drop-opaque-overlay
lappend cmd [file nativename $exepath] [file nativename $icopath] 2>@1
if {[catch $cmd output]} {
error "punkres set-icon failed: $output"
}
return $ico_count
}
#Replace RT_ICON (type 3) and RT_GROUP_ICON (type 14) in the PE at exepath with
#the images of icopath. Follows sfe-0.2.tm ReplaceIconInStub generalized: all
#existing icon/group entries (any name, any lang) are deleted and the new set
#written as ids 1..N under the first pre-existing group's name+lang (fallback
#name 1, lang 1033), in one resource-update transaction. A non-empty
#group_override replaces the group NAME only (language adoption unchanged) -
#the same override punkres's 'set-icon -group' applies, so the seam's naming
#policy keeps the two arms in parity (G-128). Raises an error on failure
#(after -discard); returns the number of images embedded. The file at exepath
#must carry NO appended payload - a resource update rewrites the image.
proc ::punkboot::kit_icon_embed_twapi {exepath icopath {group_override {}}} {
set f [open $icopath rb]
set icondata [read $f]
close $f
binary scan $icondata ttt ico_reserved ico_type ico_count
if {$ico_reserved != 0 || $ico_type != 1} {
error "$icopath is not recognisable as a .ico file"
}
if {$ico_count == 0} {
error "no images in $icopath"
}
set old_icons [list]
set old_groups [list]
set libh [twapi::load_library $exepath -datafile]
try {
set resources [twapi::extract_resources $libh]
foreach {restype var} {3 old_icons 14 old_groups} {
if {[dict exists $resources $restype]} {
foreach rname [dict keys [dict get $resources $restype]] {
foreach rlang [dict keys [dict get $resources $restype $rname]] {
lappend $var [list $rname $rlang]
}
}
}
}
} trap {TWAPI_WIN32 1812} {} {
#no resource section at all - nothing to enumerate
} finally {
twapi::free_library $libh
}
set group_name 1
set group_lang 1033
if {[llength $old_groups]} {
lassign [lindex $old_groups 0] group_name group_lang
}
if {$group_override ne ""} {
set group_name $group_override
}
set update_h [twapi::begin_resource_update $exepath]
try {
foreach entry $old_icons {
lassign $entry rname rlang
twapi::delete_resource $update_h 3 $rname $rlang
}
foreach entry $old_groups {
lassign $entry rname rlang
twapi::delete_resource $update_h 14 $rname $rlang
}
set grouphdr [binary format ttt 0 1 $ico_count]
for {set i 0} {$i < $ico_count} {incr i} {
#.ico ICONDIRENTRY is 16 bytes; GRPICONDIRENTRY is 14 - the trailing
#4-byte image offset becomes a 2-byte icon resource id. Raw width and
#height bytes are carried as-is (0 means 256 in both formats).
set off [expr {6 + 16 * $i}]
binary scan $icondata "@${off} cu cu cu cu tu tu nu nu" w h cc rsv planes bits bytesinres imgoff
set iconid [expr {$i + 1}]
append grouphdr [binary format "cu cu cu cu tu tu nu tu" $w $h $cc $rsv $planes $bits $bytesinres $iconid]
twapi::update_resource $update_h 3 $iconid $group_lang [string range $icondata $imgoff [expr {$imgoff + $bytesinres - 1}]]
}
twapi::update_resource $update_h 14 $group_name $group_lang $grouphdr
} on error {msg ropts} {
catch {twapi::end_resource_update $update_h -discard}
return -options $ropts $msg
}
twapi::end_resource_update $update_h
return $ico_count
}
#G-057 single internal entry point. Decides the kit's icon (override: root
#punkshell.ico in the kit's own custom .vfs folder, pre-merge; else the project
#default src/runtime/punkshell.ico), writes/refreshes the kit's
#<kitname>.resources.toml sidecar in the build folder, and - when the target
#has PE resources and this host can write them - stamps a per-kit copy of the
#payload-free raw runtime prefix. Returns {wrap_runtime sidecarpath}:
#wrap_runtime is the runtime prefix the assembly step must consume (the stamped
#copy when embedding happened, the untouched raw prefix otherwise).
proc ::punkboot::kit_icon_process {targetkit rt_target rtname kit_type sourcefolder vfstail raw_runtime buildfolder} {
set repo_root [file dirname $sourcefolder]
set override_icon $sourcefolder/vfs/$vfstail/punkshell.ico
if {[file isfile $override_icon]} {
set icon_origin override
set icon_path $override_icon
} else {
set icon_origin default
set icon_path $sourcefolder/runtime/punkshell.ico
}
set have_icon [file isfile $icon_path]
#RT_GROUP_ICON name policy (G-128, user decision 2026-07-29): derived from
#the icon FILE's rootname, uppercased per the RC convention - punkshell.ico
#gives PUNKSHELL for the default icon and for per-kit overrides alike
#(overrides are by convention also named punkshell.ico). Applied by
#whichever mechanism embeds; language stays adopted from the replaced group.
set icon_group ""
if {$have_icon} {
set icon_group [string toupper [file rootname [file tail $icon_path]]]
}
set embedding none
set status ""
set reason ""
if {![string match "win32-*" $rt_target]} {
set status not-applicable
set reason "target $rt_target has no PE resources"
} elseif {$rtname eq "-"} {
set status not-applicable
set reason "runtimeless .kit artifact has no PE container"
} elseif {$kit_type in {cookit cookfs}} {
set status unavailable
set reason "kit type $kit_type edits the payload in place inside the mounted runtime - no payload-free stub moment to stamp"
} elseif {!$have_icon} {
set status unavailable
set reason "icon file not found at $icon_path"
} elseif {$raw_runtime in {{} -} || ![file isfile $raw_runtime]} {
set status unavailable
set reason "extraction produced no raw runtime prefix to stamp"
} else {
#mechanism selection (G-128): the portable stamper when built, else the
#twapi arm; only when neither serves does the step skip - the combined
#reason names both gaps and the punkres build remedy
lassign [::punkboot::kiticon::punkres_available $repo_root] punkres_ok punkres_info
if {!$punkres_ok} {
lassign [::punkboot::kiticon::twapi_available] twapi_ok twapi_reason
if {!$twapi_ok} {
set status unavailable
set reason "$twapi_reason; $punkres_info"
}
}
}
set wrap_runtime $raw_runtime
if {$status eq ""} {
set stamped $buildfolder/iconed_$targetkit
file copy -force $raw_runtime $stamped
catch {exec chmod +w $stamped}
if {$punkres_ok} {
set embedding punkres
set embed_cmd [list ::punkboot::kit_icon_embed_punkres $punkres_info $stamped $icon_path $icon_group]
} else {
set embedding twapi
set embed_cmd [list ::punkboot::kit_icon_embed_twapi $stamped $icon_path $icon_group]
}
if {[catch $embed_cmd embed_result]} {
::punkboot::print_build_warnings [list "kit icon embedding FAILED for $targetkit ($icon_origin icon $icon_path via $embedding): $embed_result - kit keeps its runtime's own icon"]
set status failed
set reason $embed_result
catch {file delete $stamped}
} else {
set status embedded
set wrap_runtime $stamped
puts stdout " kit icon (G-057): embedded $icon_origin icon into $targetkit stub via $embedding ($embed_result images from [file tail $icon_path])"
}
} elseif {$status eq "not-applicable"} {
puts stdout " kit icon (G-057): embedding NOT APPLICABLE for $targetkit - $reason (sidecar still written)"
} else {
puts stdout " kit icon (G-057): embedding UNAVAILABLE for $targetkit - $reason (sidecar still written)"
}
set esc ::punkboot::kiticon::toml_escape
set lines [list]
lappend lines "# kit resource record (schema 1) for the kit artifact named by this file"
lappend lines "# with the .resources.toml suffix stripped. Emitted by the make.tcl bake"
lappend lines "# icon step (G-057). Optional and advisory; format: bin/AGENTS.md."
lappend lines "schema = 1"
lappend lines "kit = \"[$esc $targetkit]\""
lappend lines "target = \"[$esc $rt_target]\""
lappend lines "kit_type = \"[$esc $kit_type]\""
lappend lines "icon_origin = \"$icon_origin\""
lappend lines "icon_source = \"[$esc [punkcheck::lib::path_relative $repo_root $icon_path]]\""
if {$have_icon} {
lappend lines "icon_hash = \"sha256:[::punkboot::kiticon::sha256_file $icon_path]\""
set ao [::punkboot::kiticon::assetorigin_read $icon_path]
if {[dict exists $ao source]} {
lappend lines "icon_provenance_source = \"[$esc [dict get $ao source]]\""
}
if {[dict exists $ao source_hash]} {
lappend lines "icon_provenance_source_hash = \"[$esc [dict get $ao source_hash]]\""
}
lappend lines "icon_group = \"[$esc $icon_group]\""
}
lappend lines "embedding = \"$embedding\""
lappend lines "embedding_status = \"$status\""
lappend lines "embedding_reason = \"[$esc $reason]\""
set sidecar $buildfolder/$targetkit.resources.toml
::punkboot::kiticon::writefile_ifchanged $sidecar "[join $lines \n]\n"
return [list $wrap_runtime $sidecar]
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set startdir [pwd]
puts stdout "Found [llength $vfs_tails] .vfs folders - checking each for executables that may need to be built"
cd [file dirname $buildfolder]
@ -6461,6 +6964,11 @@ foreach vfstail $vfs_tails {
$vfs_event targetset_addsource $sourcefolder/vfs/_config ;#some files linked via fauxlink - need to detect change
$vfs_event targetset_addsource $sourcefolder/vfs/_vfscommon.vfs
$vfs_event targetset_addsource $sourcefolder/vfs/$vfstail
if {[file isfile $sourcefolder/runtime/punkshell.ico]} {
#G-057: the default kit icon is a build input - a changed icon rebuilds
#kits (an override icon lives inside the .vfs folder, already a source)
$vfs_event targetset_addsource $sourcefolder/runtime/punkshell.ico
}
if {$rtname ne "-"} {
set building_runtime $buildfolder/build_$runtime_fullname ;#working copy of runtime executable - (possibly with kit/zipfs/cookfs etc attached!)
$vfs_event targetset_addsource $building_runtime
@ -6857,6 +7365,13 @@ foreach vfstail $vfs_tails {
continue
}
#G-057 kit icon step: sidecar for every kit; PE icon embedding where
#the target has PE resources and this host can write them. Stub-first:
#the assemblers below consume wrap_runtime - the stamped per-kit copy
#of the payload-free raw prefix when embedding happened, the untouched
#raw prefix otherwise. Runtime store originals are never modified.
lassign [::punkboot::kit_icon_process $targetkit $rt_target $rtname $target_kit_type $sourcefolder $vfstail $raw_runtime $buildfolder] wrap_runtime kiticon_sidecar
set wrapvfs $targetvfs
switch -- $target_kit_type {
zip {
@ -6886,14 +7401,14 @@ foreach vfstail $vfs_tails {
}
if {[info commands ::tcl::zipfs::mkimg] ne ""} {
#note - as at 2024-08 - there is some discussion about the interface to mkimg - it is considered unstable (may change to -option value syntax)
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $raw_runtime"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $raw_runtime
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $wrap_runtime"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $wrap_runtime
} else {
#G-122: the driving tcl has no zipfs (8.6) - assemble the same
#kind of image by concatenation. The result mounts on any
#zipfs-capable runtime (archive-start-relative offsets).
puts stderr "WARNING: tcl shell '[info nameofexecutable]' has no zipfs - assembling zip kit $targetkit by concatenation (raw runtime + mkzip)"
::punkboot::assemble_zipcat_image $raw_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
::punkboot::assemble_zipcat_image $wrap_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
}
} result ]} {
set failmsg "zipfs mkimg failed with msg: $result"
@ -6930,7 +7445,7 @@ foreach vfstail $vfs_tails {
}
#'archive' based zip offsets - editable in 7z,peazip
::punkboot::assemble_zipcat_image $raw_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
::punkboot::assemble_zipcat_image $wrap_runtime $wrapvfs $buildfolder/$vfsname.new $buildfolder/$vfsname.zip
} result ]} {
set failmsg "creating zipcat image failed with msg: $result"
puts stderr "creating image (zipcat) $targetkit failed"
@ -7005,13 +7520,13 @@ foreach vfstail $vfs_tails {
cd [file dirname $building_runtime] ;#make sure SDX is called with the same working dir as the files we're working on.
if {[catch {
if {$rtname ne "-"} {
exec {*}$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $raw_runtime {*}$verbose
exec {*}$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $wrap_runtime {*}$verbose
} else {
exec {*}$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs {*}$verbose
}
} result]} {
if {$rtname ne "-"} {
set sdxmsg "$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $raw_runtime {*}$verbose failed with msg: $result"
set sdxmsg "$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs -runtime $wrap_runtime {*}$verbose failed with msg: $result"
} else {
set sdxmsg "$::sdxpath wrap $buildfolder/$vfsname.new -vfs $wrapvfs {*}$verbose failed with msg: $result"
}
@ -7175,6 +7690,21 @@ foreach vfstail $vfs_tails {
# -- --- --- --- --- ---
$vfs_event targetset_end OK
#G-134 post-assembly offset-style pin: the pipeline emits
#ARCHIVE-relative zip payloads by construction - this probe makes
#that a checked contract. Advisory per the G-133 posture: a
#file-relative image earns a recapped BUILD-WARNING (the G-128
#stamper would refuse it by default) and the kit still builds and
#deploys; plain/none (no attached zip - the metakit kit shape) and
#unreadable results are silence. One EOCD scan + CD walk per
#REBUILT kit only.
lassign [::punkboot::get_kit_offsetstyle_report $buildfolder/$targetkit] offprobe_available offprobe
if {!$offprobe_available} {
puts stderr "NOTE: kit offset-style pin unavailable (punkboot::utils kit_offsetstyle_report not loadable from bootsupport) - continuing without it"
} elseif {[dict get $offprobe offsetstyle] eq "file"} {
::punkboot::print_build_warnings [list "kit $targetkit assembled with a FILE-relative zip payload (G-134 pin: the pipeline emits archive-relative by construction; the G-128 stamper refuses file-relative by default) - investigate this kit's assembly path"]
}
#G-133 post-build smoke-require probe. Only an actual 'package require'
#inside the built artifact observes package RESOLUTION - the wrong-arch
#version-shadowing class is invisible to every structural check because
@ -7249,6 +7779,10 @@ foreach vfstail $vfs_tails {
puts stdout "$deployment_folder/$targetkit"
after 300
file copy $buildfolder/$targetkit $deployment_folder/$targetkit
#G-057: the kit resource sidecar ships with the kit
if {[file isfile $buildfolder/$targetkit.resources.toml]} {
file copy -force $buildfolder/$targetkit.resources.toml $deployment_folder/$targetkit.resources.toml
}
lappend installed_kits $targetkit
# -- ----------
$bin_event targetset_end OK
@ -7257,6 +7791,12 @@ foreach vfstail $vfs_tails {
set skipped_kit_install 1
puts stderr "."
puts stdout "Skipping kit install for $targetkit with vfs $vfstail runtime $rtname - no change detected"
#G-057: keep the deployed resource sidecar current even when the kit
#itself is unchanged (the record can be newer than the kit rebuild -
#e.g the sidecar feature arriving, or a host-capability change)
if {[::punkboot::kiticon::copy_ifchanged $buildfolder/$targetkit.resources.toml $deployment_folder/$targetkit.resources.toml] eq "copied"} {
puts stdout " kit icon (G-057): refreshed deployed sidecar $deployment_folder/$targetkit.resources.toml"
}
lappend skipped_kit_installs [list kit $targetkit reason "no change detected"]
$bin_event targetset_end SKIPPED
}

119
src/tests/modules/punkboot/utils/testsuites/utils/offsetstyle.test

@ -0,0 +1,119 @@
# -*- tcl -*-
# Tests for punkboot::utils::kit_offsetstyle_report - the G-134 advisory
# archive-relative output pin's probe predicate (a thin classification wrapper
# over punk::zip::archive_info, in punkboot::utils precisely so it is testable
# without a bake):
# - plain zip (no prefix) -> plain
# - runtime-prefixed zip, archive-relative offsets (the pipeline's shape) -> archive
# - runtime-prefixed zip, FILE-relative offsets (mkzip -offsettype file - the
# deliberately-available manual convention; the shape that earns the bake's
# BUILD-WARNING) -> file
# - non-zip files (text, binary junk - the metakit kit shape's class) -> none
# - missing path / unusable input -> unreadable with a detail message
# - baseline: every assembled kit image in src/_build (when a build has run)
# probes archive or none, never file - the G-124-measured property the pin
# asserts
# All fixtures are GENERATED here with punk::zip::mkzip / binary writes - no
# committed binaries, nothing executed.
# Run: tclsh src/tests/runtests.tcl -report compact -show-passes 0 -include-paths modules/punkboot/utils/*** offsetstyle.test
package require tcltest
package require punkboot::utils
package require punk::zip
#added 2026-07-29 (agent, G-134)
namespace eval ::testspace {
namespace import ::tcltest::*
variable BASE [makeDirectory g134_offsetstyle]
#<projectroot>/src/tests/modules/punkboot/utils/testsuites/utils -> 7 levels up
variable projectroot [file normalize [file join [file dirname [info script]] .. .. .. .. .. .. ..]]
variable buildfolder [file join $projectroot src _build]
testConstraint builtkitsavailable [expr {[llength [glob -nocomplain -type f -directory $buildfolder *.exe]] > 0}]
proc writefile {path content} {
set f [open $path wb]
puts -nonewline $f $content
close $f
return $path
}
#shared fixture set: a tiny payload tree, a fake runtime prefix, and the
#three zip shapes built from them
variable payloaddir [file join $BASE payload]
file mkdir $payloaddir
writefile [file join $payloaddir main.tcl] "puts {offsetstyle fixture payload}\n"
writefile [file join $payloaddir data.txt] [string repeat "0123456789" 20]
variable stub [writefile [file join $BASE fakestub.bin] "FAKE-RUNTIME-PREFIX-[string repeat X 200]"]
variable zip_plain [file join $BASE plain.zip]
punk::zip::mkzip -directory $payloaddir -base $payloaddir $zip_plain *
variable kit_archive [file join $BASE kit_archive.exe]
punk::zip::mkzip -runtime $stub -directory $payloaddir -base $payloaddir $kit_archive *
variable kit_file [file join $BASE kit_file.exe]
punk::zip::mkzip -runtime $stub -offsettype file -directory $payloaddir -base $payloaddir $kit_file *
test kit_offsetstyle_plain {a bare zip probes as plain} -body {
variable zip_plain
set r [punkboot::utils::kit_offsetstyle_report $zip_plain]
list offsetstyle [dict get $r offsetstyle] detail [dict get $r detail]
} -result {offsetstyle plain detail {}}
test kit_offsetstyle_archive {a runtime-prefixed archive-relative image (the pipeline shape) probes as archive} -body {
variable kit_archive
set r [punkboot::utils::kit_offsetstyle_report $kit_archive]
list offsetstyle [dict get $r offsetstyle] dataoffset_positive [expr {[dict get $r dataoffset] > 0}]
} -result {offsetstyle archive dataoffset_positive 1}
test kit_offsetstyle_file {a runtime-prefixed FILE-relative image (mkzip -offsettype file) probes as file - the warning shape} -body {
variable kit_file
set r [punkboot::utils::kit_offsetstyle_report $kit_file]
dict get $r offsetstyle
} -result {file}
test kit_offsetstyle_none_text {a non-zip text file probes as none (silence - the pin covers offset convention, not zip presence)} -body {
variable BASE
set f [writefile [file join $BASE not_a_zip.txt] "just text, no archive here\n"]
set r [punkboot::utils::kit_offsetstyle_report $f]
dict get $r offsetstyle
} -result {none}
test kit_offsetstyle_none_binary {a non-zip binary file (the metakit kit shape's class) probes as none} -body {
variable BASE
set f [file join $BASE not_a_zip.bin]
set fd [open $f wb]
puts -nonewline $fd [binary format a4c* "JL\x1a\x00" {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16}]
close $fd
set r [punkboot::utils::kit_offsetstyle_report $f]
dict get $r offsetstyle
} -result {none}
test kit_offsetstyle_missing {a missing path probes as unreadable with a detail message} -body {
variable BASE
set r [punkboot::utils::kit_offsetstyle_report [file join $BASE no_such_file.exe]]
list offsetstyle [dict get $r offsetstyle] has_detail [expr {[dict get $r detail] ne ""}]
} -result {offsetstyle unreadable has_detail 1}
test kit_offsetstyle_build_baseline {every assembled kit image in src/_build probes archive or none - never file (the G-124 baseline the pin asserts)} -constraints {builtkitsavailable} -body {
variable buildfolder
set bad [list]
foreach kitimage [lsort [glob -nocomplain -type f -directory $buildfolder *.exe]] {
#kit images only: skip runtime prefixes and helper artifacts the
#build folder also holds (raw_/iconed_/build_ prefixes)
set tail [file tail $kitimage]
if {[string match "raw_*" $tail] || [string match "iconed_*" $tail] || [string match "build_*" $tail]} {
continue
}
set r [punkboot::utils::kit_offsetstyle_report $kitimage]
if {[dict get $r offsetstyle] eq "file"} {
lappend bad $tail
}
}
set bad
} -result {}
cleanupTests
}
namespace delete ::testspace

21
src/tests/shell/testsuites/punkexe/maketclpayloadcheck.test

@ -129,6 +129,27 @@ namespace eval ::testspace {
}\
-result {active 1 crosstarget 1 recapped 1 exemption 1 seesresolution 1 skipstated 1 undeclared 1}
#added 2026-07-29 (agent, G-134)
test maketcl_check_offsetstyle_pin {make.tcl check reports the kit offset-style pin with one of the two documented statuses and states its contract (advisory, file-relative warning, plain/none silence)}\
-constraints {punkexeavailable} -body {
set r [maketcl_run {check}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result pinline [regexp -line {^kit offset-style pin \(G-134\): (ACTIVE \(advisory\)|UNAVAILABLE)} $out]
#contract lines shown when ACTIVE (the current expected state; an
#UNAVAILABLE snapshot legitimately omits them - gate on the status)
if {[regexp -line {^kit offset-style pin \(G-134\): ACTIVE \(advisory\)\s*$} $out]} {
lappend result probe [regexp {punk::zip::archive_info} $out]
lappend result warnshape [regexp {FILE-relative offsets} $out]
lappend result silence [regexp {plain/none results.*are silence} $out]
} else {
lappend result probe 1 warnshape 1 silence 1
}
set result
}\
-result {timedout 0 exitcode 0 pinline 1 probe 1 warnshape 1 silence 1}
#added 2026-07-27 (agent, G-133) - the declared list characterizes the current
#src/runtime/mapvfs.config smoke-require matrix (deliberate config changes update it)
test maketcl_check_payloadchecks_declared_kits {check lists the kits currently declaring smoke-require packages}\

BIN
src/vfs/_vfscommon.vfs/modules/punk/mix/templates-0.2.0.tm

Binary file not shown.

67
src/vfs/_vfscommon.vfs/modules/punkboot/utils-0.5.0.tm → src/vfs/_vfscommon.vfs/modules/punkboot/utils-0.6.0.tm

@ -7,7 +7,7 @@
# (C) 2023
#
# @@ Meta Begin
# Application punkboot::utils 0.5.0
# Application punkboot::utils 0.6.0
# Meta platform tcl
# Meta license BSD
# @@ Meta End
@ -23,7 +23,7 @@ package require Tcl 8.6-
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punkboot::utils {
variable version 0.1.0
namespace export binary_arch_classify parse_punkproject_version read_punkproject_version read_changelog_latest_version vcs_dirty_warnings vfs_binary_arch_report vfs_boot_library_report
namespace export binary_arch_classify kit_offsetstyle_report parse_punkproject_version read_punkproject_version read_changelog_latest_version vcs_dirty_warnings vfs_binary_arch_report vfs_boot_library_report
namespace eval argdoc {
@ -736,6 +736,65 @@ namespace eval punkboot::utils {
dict set report ok [expr {[llength $mismatches] == 0}]
return $report
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punkboot::utils::kit_offsetstyle_report
@cmd -name "::punkboot::utils::kit_offsetstyle_report"\
-summary\
"Advisory zip offset-style probe of an assembled kit image (G-134)"\
-help\
"Probes a finished kit image file with punk::zip::archive_info and
reports the offset convention of any attached zip payload. The
punkshell bake pipeline emits ARCHIVE-relative payloads by
construction; this probe makes that a checked contract - a
'file' result is the pipeline-regression signal make.tcl
surfaces as a recapped BUILD-WARNING (advisory: the kit still
builds and deploys). 'plain' (the file is a bare zip), 'none'
(no zip attached - e.g the metakit kit shape, or any non-zip
file) and 'unreadable' are silence, not warnings: the pin
covers the offset convention of an attached zip, never the
presence of one. Purely structural: one EOCD scan plus a
central-directory walk, nothing executed, so cross-target kit
images are covered.
Returned dict keys:
offsetstyle archive | file | plain | none | unreadable
dataoffset payload base offset when archive_info supplies
one (\"\" otherwise)
detail diagnostic text for unreadable (missing file,
punk::zip unavailable, archive_info error);
empty otherwise"
@leaders
kitpath -type string -optional 0 -help\
"Path of the assembled kit image file to probe"
}]
}
proc kit_offsetstyle_report {kitpath} {
set report [dict create offsetstyle unreadable dataoffset "" detail ""]
if {![file isfile $kitpath]} {
dict set report detail "no such file: $kitpath"
return $report
}
if {[catch {package require punk::zip} errM]} {
dict set report detail "punk::zip not available: $errM"
return $report
}
if {[catch {punk::zip::archive_info $kitpath} zinfo]} {
dict set report detail "archive_info error: $zinfo"
return $report
}
if {[dict exists $zinfo offsetstyle]} {
dict set report offsetstyle [dict get $zinfo offsetstyle]
} else {
dict set report detail "archive_info returned no offsetstyle"
return $report
}
if {[dict exists $zinfo dataoffset]} {
dict set report dataoffset [dict get $zinfo dataoffset]
}
return $report
}
}
namespace eval ::punk::args::register {
@ -746,8 +805,8 @@ namespace eval ::punk::args::register {
## Ready
package provide punkboot::utils [tcl::namespace::eval punkboot::utils {
variable version
#- this version number, exactly 0.5.0, is a literal used in src module folders
#- this version number, exactly 0.6.0, is a literal used in src module folders
#- we refer to this sometimes as the magic version number
set version 0.5.0
set version 0.6.0
}]
return
Loading…
Cancel
Save