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

24 KiB

G-057 Kit builds carry a configurable icon: text sidecar for every target, embedded PE resources for windows targets

Status: achieved 2026-07-29 Scope: src/make.tcl (kit/zipkit wrap steps); src/runtime/punkshell.ico (project default, existing); src/vfs/*.vfs (override placement convention); src/runtime/mapvfs.config (only if an explicit config element is the chosen override mechanism); TEMP_REFERENCE/tcl-sfe (read-only reference); helper proc location decided in the work (make.tcl inline vs punk::mix lib); kit icon sidecar record (emission points recorded in the work) Goal: kit/zipkit builds record a build-time icon choice for every target - defaulting to the project icon src/runtime/punkshell.ico and overridable per kit by its .vfs folder - in a text sidecar that ships with the kit whatever its target, and embed it as PE icon resources when the target has them and the build host can write them - by replacing the icon resources in the built executable using the twapi-based mechanism demonstrated in tcl-sfe (TEMP_REFERENCE/tcl-sfe, by twapi author and Tcl core member Ashok P. Nadkarni): RT_ICON/RT_GROUP_ICON replacement via twapi resource-update APIs, applied so the appended vfs payload stays intact (icon the stub before appending, or sfe-style split/update/reattach), behind a single mechanism seam so the embedding implementation can be replaced without touching its callers. Acceptance: a make.tcl bake on a windows host producing a win32-target kit produces kit executables whose embedded icon resources are the project default punkshell.ico, and a kit whose .vfs supplies an override icon gets that icon instead (verified by resource inspection, e.g twapi::extract_resources, not just Explorer eyeballing); the icon-replaced executables still boot to a working punk shell reading their vfs payload for the kit types we build (kit, zip, zipcat per mapvfs.config); runtimes under src/runtime are never modified - replacement applies to the built copies only; a target with no PE resources (linux, freebsd, macosx) skips embedding as NOT APPLICABLE rather than as degradation and still gets its sidecar, while a win32 target on a host that cannot perform the update (twapi unavailable, or a non-windows host cross-baking a windows kit) skips embedding with a DISTINCT notice naming which condition applied, still writes the sidecar, and the build otherwise completes unchanged - the cross-host case is recorded here as a known shortcoming with its remedy; the sidecar is produced for every kit on every target, carries the same icon data the PE embedding uses (source .ico identity and provenance) in a documented plain-text form, is byte-stable across rebuilds at unchanged input, and is defined so the parked RT_VERSION follow-on extends the same file rather than adding a second one; the embedding implementation sits behind a single internal entry point with the mechanism selected inside it, so a later portable replacement is a substitution rather than a rewrite; the twapi mechanism remains available INDEFINITELY as the no-toolchain windows path - the seam SELECTS between mechanisms rather than replacing one with the other - and a fresh checkout that fetches a runtime and bakes produces iconed kits with no build toolchain installed; rebuilds are idempotent (re-wrapping an already-iconed build copy converges, no resource accumulation); the override convention (filename/location in the kit's custom .vfs folder vs a mapvfs.config element) and the stub-vs-split ordering decision are recorded in the detail file with the tcl-sfe attribution - the override is SHARED across a kit definition's targets and carries no target dimension (decided 2026-07-26): it is used where embedding applies and ignored elsewhere.

Context

Built punk kit executables currently carry whatever icon resource their runtime stub shipped with. The project icon src/runtime/punkshell.ico exists and is COPIED into vfs trees (a tclkit-era convention), but nothing embeds it into the produced executable's Windows resources - so all kits look like their runtime in Explorer and the taskbar.

Renamed 2026-07-28 from punk1.ico, and its artwork replaced from the src/assets/logo/ master at the same time. The four build-consumed copies (src/runtime/, _vfscommon.vfs, mkzipfix.vfs, punk8_statictwapi.vfs) are now punkshell.ico and are copies of src/assets/logo/punk-mark.ico; the pre-2026-07 punk1 artwork is retained at src/assets/logo/legacy/ and is the only place that name survives.

Host/target reopened this (2026-07-26). The goal was drafted before G-122 separated what the driving tclsh IS from what a bake is FOR, so its skip condition read as one host-shaped test ("non-Windows platform") while its Notes used target language ("Linux/other-platform kit outputs have no PE resources"). Those are now two independent conditions: a target with no PE resources is not applicable, and a windows target on a host that cannot run twapi is a genuine shortcoming. Measured the same day on our own runtimes: .rsrc is generally NOT the last section (tclsfe-x64/punk91: .text .rdata .data .pdata .rsrc .reloc; suite-built tclsh9.0.5-punk: ... .rsrc .reloc), so any future non-twapi implementation must append a section and repoint the resource data directory rather than grow .rsrc in place.

The reference mechanism is tcl-sfe (TEMP_REFERENCE/tcl-sfe, read-only), written by twapi author and Tcl core member Ashok P. Nadkarni: library/sfe-0.2.tm method replaceIcon / ReplaceIconInStub replaces RT_ICON (type 3) and the icon group (RT_GROUP_ICON, type 14) via twapi::extract_resources / twapi::begin_resource_update / twapi::delete_resource / twapi::update_resource / twapi::end_resource_update, building the group entry from the .ico file's own image directory.

Approach

  • Hook the icon step into make.tcl's kit wrap processing (driven by src/runtime/mapvfs.config), operating on the BUILD COPY of the runtime stub - never on the originals under src/runtime.
  • Payload integrity is the critical ordering constraint: a Windows resource update rewrites the PE image and corrupts any already-appended vfs payload. Either icon the stub copy BEFORE appending the zip/metakit payload, or use the sfe approach of splitting payload off, updating resources, and reattaching (sfe-0.2.tm does the split/update/reattach dance for exactly this reason). CHOSEN (2026-07-29): stub-first. The bake's extraction phase already emits a payload-free raw runtime prefix (_build/raw_<runtime>) for every PE-relevant kit type, and all three assemblers consume it (tcl::zipfs::mkimg and assemble_zipcat_image take the raw exe; sdx wrap -runtime takes the mksplit .head) - so the seam stamps a PER-KIT COPY of that raw prefix before assembly and hands the stamped copy to the assembler. No overlay exists at UpdateResource time, making the corruption class structurally impossible, and a per-kit copy (not an in-place stamp of the shared raw_runtime) keeps kits of the same runtime independently overridable. sfe's split/update/reattach exists because sfe retrofits FINISHED sfx files; the bake owns the pre-assembly moment, so it uses it. Post-hoc stamping of finished kits stays G-128 territory (with the tcl-sfe attribution: the split/update/reattach prior art is Ashok P. Nadkarni's sfe-0.2.tm).
  • Default icon: src/runtime/punkshell.ico. Per-kit override supplied by the kit's .vfs. IMPORTANT wrinkle for the override convention: _vfscommon.vfs already merges a punkshell.ico into every built vfs tree, so override detection must consult the kit's own CUSTOM .vfs folder (pre-merge, under src/vfs/) - or use an explicit mapvfs.config element - otherwise every kit would appear to "override" with the same common file. CHOSEN (2026-07-29): a root-level punkshell.ico in the kit's own custom folder (src/vfs/<kit>.vfs/punkshell.ico, consulted pre-merge) is the override; no mapvfs element (if a config-driven override is ever wanted it belongs in G-024's toml grammar). This buys a coherence property for free: the overlay merge order (_vfscommon then custom) puts the same file into the shipped payload that the embedding stamps into the PE, so a kit always wears the icon it carries. The override is shared across a kit definition's targets (2026-07-26 decision, in the Acceptance): used where embedding applies, ignored elsewhere.
  • Sidecar first, embedding second. The icon CHOICE is recorded in a plain-text record emitted for every kit on every target; PE embedding is one platform-specific projection of that record. Non-PE targets therefore carry the same information, and the parked RT_VERSION follow-on extends the same file rather than inventing another.
  • Mechanism seam: one internal entry point performs the embedding and selects the mechanism inside itself. twapi is the mechanism now (demonstrated prior art, windows host, no new build infrastructure); a portable post-hoc PE writer is the intended replacement and must be a substitution, not a rewrite of the callers.
  • Graceful degradation, split by axis: a target with no PE resources skips as not applicable; a windows target on a host that cannot run twapi skips with its own notice. Both still write the sidecar and the build proceeds unchanged (per the src/AGENTS.md optional-extension guidance).
  • Idempotence: re-running a wrap on an already-processed build copy must converge (sfe deletes existing icon/group resources before writing - follow that).

Decisions (2026-07-29, implementation)

  • Sidecar name and format: <kitname>.resources.toml beside the artifact (emitted into src/_build/, deployed beside bin/<kitname>). Named for windows RESOURCES, not icons, so the parked RT_VERSION follow-on extends the same file. Schema 1, one flat TOML root table in the tolerant format family G-135 established (unknown keys skipped, unrecognised schema ignored): schema = 1, kit, target, kit_type, icon_origin (default|override), icon_source (path relative to the punkshell repo root), icon_hash (sha256:-prefixed), icon_provenance_source/icon_provenance_source_hash (lifted from the chosen icon's .assetorigin.toml when one exists - the G-135 chain, so the record reaches back to the SVG-derived master), embedding (twapi|none), embedding_status (embedded|not-applicable|unavailable|failed), embedding_reason (empty when embedded). Byte-stability: no timestamps, deterministic field order, written only when bytes differ; stability is per unchanged input AND host capability (a cross-host bake legitimately records a different embedding_status than a windows-host bake of the same kit).
  • Seam home: make.tcl inline (::punkboot::kit_icon_process is the single internal entry point; ::punkboot::kit_icon_embed_twapi is the mechanism it selects today). Rationale: bake-orchestration glue with one consumer, and inline keeps the fresh-checkout no-toolchain persona free of bootsupport-staleness coupling (the guarded-require degradation the G-125 and G-133 checks accept - both achieved, archived at goals/archive/G-125-unbootable-kit-deploy-gate.md and goals/archive/G-133-bake-payload-consistency-checks.md - is wrong for a step the acceptance requires on a fresh checkout). Revisit to a punk:: module if a second consumer appears (the G-128 parity harness is the likely one).
  • Mechanism (generalized from sfe-0.2.tm ReplaceIconInStub): enumerate existing RT_GROUP_ICON (type 14) and RT_ICON (type 3) via twapi::load_library -datafile + twapi::extract_resources (tolerating TWAPI_WIN32 1812/1814 = no resource section / no such resource); then in one begin_resource_update transaction delete ALL type-3 and type-14 entries and write the new icons as ids 1..N plus one group entry - the .ico's 16-byte ICONDIRENTRYs converted to 14-byte GRPICONDIRENTRYs (icon id replaces the image offset; 0 width/height read as 256) - under the first pre-existing group's name+lang (fallback name 1, lang 1033), committing with end_resource_update (-discard on error). Delete-all-then-write converges (idempotence), and every bake re-splits the raw prefix from the pristine runtime anyway. Differences from sfe recorded deliberately: sfe preserves its known single group entry in place and reuses ids (and its new-id branch has a latent maxExistinIconId typo that errors when the new .ico carries more images than the old group - not copied); we replace all groups so a multi-group stub cannot present a stale first group to Explorer's lowest-entry pick.
  • Skip taxonomy (each with its own notice, sidecar always written): non-PE target -> not applicable; runtimeless - .kit artifact -> not applicable (no PE container); cookfs/cookit kit type -> unavailable (payload is edited in place inside the mounted runtime - no payload-free stub moment exists); win32 target with host process-family != windows -> unavailable, cross-host (remedy: G-128); twapi/twapi_resource not loadable -> unavailable, named; raw runtime prefix missing (extraction did not produce one) -> unavailable, named. A twapi error during embedding is a recapped BUILD-WARNING and embedding_status = "failed" - advisory in the G-133 manner, the kit still builds and deploys with the runtime's own icon.
  • Change detection: src/runtime/punkshell.ico is added to the kit's punkcheck source set, so a default-icon change rebuilds kits; an override icon lives inside the kit's .vfs folder, which is already a source.

Follow-on candidate goal (flagged, not yet a GOALS.md entry)

Version-information resource stamping (RT_VERSION, type 16) - sfe-0.2.tm already demonstrates the resource construction (BuildVersionResource, string tables, delete-then-write). Requirements sketch agreed 2026-07-10:

  • Copyright: defaults to the copyright already present in the INPUT kit runtime executable's version resource (carry it over - the runtime authors' copyright is not ours to silently drop), while being easily configurable - initially from punkproject.toml and/or per-vfs config.
  • Product Name: should indicate the core Tcl version of the runtime by default (e.g. carrying the Tcl major.minor the kit embeds), similarly configurable via punkproject.toml / per-vfs config.
  • Other fields (FileVersion/ProductVersion from the project version, FileDescription etc.) to be settled when the goal is drafted.
  • Config surface should anticipate the toml direction already agreed for build configs (punkproject.toml now; buildsuites toml era later).

Notes

  • tclsfe-x64.exe is already one of our mapped runtimes (mapvfs.config) - the same author's stub and the reference mechanism, so it makes a good first test subject alongside a tclkit-style runtime.
  • Resource inspection for acceptance: twapi::extract_resources on the built exe (compare icon group/images against the source .ico), not visual inspection.
  • Linux/other-platform kit outputs have no PE resources - explicitly out of scope; the skip path covers them.
  • G-023 relationship (recorded 2026-07-24 after overlap review; this file previously referenced nothing): its versioned/dev outputs are rewritten every build - the icon step hooks the same kit wrap steps, and the icon-before-payload-append ordering is settled against that rebuild cadence.
  • G-024 relationship: the per-kit icon override convention may become a mapvfs element; if so it lands in G-024's toml grammar, not the legacy line format.
  • G-028 relationship: a locked kit exe blocks the resource update exactly as it blocks deploy - G-028's locker-report helper should name the holder on icon-step failure too.
  • G-025/G-117 note (G-117 achieved 2026-07-24 - see goals/archive/G-117-self-describing-runtimes.md): the parked RT_VERSION follow-on above stamps version/copyright resources from punkproject.toml - when it is drafted as a goal, reconcile with G-025's kit stamp and G-117's embedded artifact record so the three provenance surfaces tell one story.
  • G-122 (achieved) - separated host from target across the kit surfaces, which is what split this goal's skip condition in two; see goals/archive/G-122-host-target-platform-split.md.
  • No-toolchain windows path, verified 2026-07-26: twapi is VENDORED and git-tracked (src/vendorlib_tcl8|9/win32-x86_64/twapi-5.0b1) and make.tcl puts $startdir/vendorlib_tcl/ on auto_path unconditionally (sourcesupport_library_paths), so the fresh-checkout persona - fetch a runtime from punkbin, bake - reaches the resource-update APIs with nothing built. Probed under both a stock system tclsh 9.0.3 (twapi 5.2.0) and a fetched plain family runtime tclsh9.0.5-punk (vendored 5.0b1): twapi_resource plus begin_resource_update / update_resource / extract_resources present in both. This is why the twapi arm is permanent rather than transitional - deleting it when a portable tool lands would silently break that user.
  • Standing tension: that path is load-bearing on a COMMITTED BINARY, which is what G-004 wants gone. The route that retires it without a toolchain is a punkbin-served stamping tool (G-128 + G-123/G-006) - the same fetch the persona already performs for runtimes.
  • Related: G-128 - the portable post-hoc implementation this goal's seam is built for.
  • Related: G-127 - once one vfs definition bakes for several targets, "which kits get an icon" becomes a target question; that is what prompted the 2026-07-26 decision recorded in the Acceptance that the override is shared across a kit definition's targets.
  • Related: G-135 (achieved 2026-07-28, archived - see goals/archive/G-135-asset-provenance-records.md) - the source-tree provenance records this goal's sidecar can read. The sidecar is emitted per kit at bake time and describes what got embedded; G-135's per-asset assetorigin sidecars are committed and describe a derivation. Populating the sidecar's "source .ico identity and provenance" from those records closes the gap where this goal's provenance otherwise stops at the .ico bytes, one tier short of the SVG master they were generated from.
  • G-135 (archived) delivered concrete inputs for this goal: the record format (src/assets/logo/AGENTS.md "Provenance records"), the checker (scriptlib/developer/assetorigin_check.tcl), and a verified derived-copy sidecar beside this goal's default icon at src/runtime/punkshell.ico (hash equals source_hash, source ../assets/logo/punk-mark.ico). The kit-shipping call its notes carried is now made (user-approved 2026-07-28): src/vfs/_vfscommon.vfs/punkshell.ico carries a sidecar that ships inside every kit merging the common payload from that kit's next bake - inside a kit it truthfully reads source-absent (bytes hash-confirmed, source not shipped). The per-kit mkzipfix.vfs / punk8_statictwapi.vfs copies stay unrecorded.
  • The sidecar is the natural reconciliation point for the three provenance surfaces this file already flags (G-025's kit stamp, G-117's embedded artifact record, the parked RT_VERSION follow-on) - one text record per kit, on every platform.
  • G-121 (archived) parameterized the wrap steps this goal's acceptance runs through: 'make.tcl bake ' now rebuilds a single kit for cheap icon-override verification, and a per-kit icon override would slot into bakelist's filtered per-kit detail - see goals/archive/G-121-bakelist-selective-bake.md
  • KNOWN SHORTCOMING (cross-host, per Acceptance): a win32-target kit baked on a host that cannot run the twapi resource APIs deploys UNSTAMPED (with its runtime's own icon), records embedding_status = "unavailable" with the distinct reason in its sidecar, and the build otherwise completes unchanged. Two conditions produce it, each with its own notice text: a non-windows build host (process family != windows - e.g a WSL/linux cross-bake), and a windows host whose driving tclsh cannot load twapi (observed live 2026-07-29: the msys2 ucrt64 mingw tclsh86 - vendored twapi 5.0b1 does not load there). REMEDY: G-128's portable post-hoc stamper - it operates on the finished kit from any host, and G-057's seam selects it when present. Until then, rebaking the affected kit from a capable windows host (or stamping later from one) closes the gap.

Progress

Implementation landed 2026-07-29 (activation b757d183). Verification evidence so far, windows host win32-x86_64 (native Tcl 9.0.3 driving unless stated), recorded as gathered:

  • Full bake: 10 of 11 buildable kits INSTALLED with the icon step live - embedded via twapi for every win32 kit (zip: punk91, punk905, punk902z, punk9bi_beta; zip win32-ix86: punkluck86, punk91ix86; kit/sdx-metakit: punkbi, punksys, punk86; punk9_beta embedded + built but failed DEPLOY on the pre-existing bin file lock - its stamped build product waits in src/_build, sidecar deliberately not deployed with it). punkmagic (the one zipcat-configured kit) is unbuildable for the pre-existing reason that its runtime tclsh90magic.exe was never present in the store - the zipcat ASSEMBLER is exercised via the zipfs-less concat path below, which is the same ::punkboot::assemble_zipcat_image call.
  • punkshell902 (kit-type, target linux-x86_64): NOT APPLICABLE notice, sidecar written with the full icon choice + provenance and embedding_status not-applicable.
  • Resource inspection (twapi::extract_resources + read_resource, not eyeballing): punk91.exe, punk86.exe (metakit), punk91ix86.exe (32-bit PE) all PASS - exactly one RT_GROUP_ICON (the stub's original group name "SFE" preserved, lang 1033), 7 entries, every RT_ICON byte-identical to src/runtime/punkshell.ico's images including the 256px PNG entry.
  • Boot (scratch copies, no user shells touched): punk91 (zip, zipfs mount, tcl_library from payload, punk::args + punk::zip load), punk86 (metakit, library inside mounted exe path), punk905 (zip) - all BOOT-OK reading their payloads.
  • Override round-trip: alternative/punk-mark-solid.ico placed as src/vfs/punk9wintk903.vfs/punkshell.ico -> bake punk91 -> sidecar icon_origin "override" with the override's path + hash (distinct artwork hash 2e91ae70...), embedded resources match the SOLID ico exactly; provenance fields correctly absent (override carries no assetorigin sidecar). Override file removed, full bake restored the default - deployed punk91 verified back on punkshell.ico.
  • Idempotence: kit_icon_embed_twapi run twice more on an already-stamped stub copy - identical file size both times, resource inspection still exactly 7+1 entries byte-matching the ico (no accumulation, converged). Every bake additionally restarts from a pristine re-split raw prefix by construction.
  • Sidecar byte-stability: independent rebuilds at unchanged input emit byte-identical records (the punk91 selective bake and the later full bake produced the same sidecar bytes; no timestamps by design, and the emitter rewrites the file only when bytes change).
  • No-toolchain windows path: embedding driven end-to-end under the fetched family runtime tclsh9.0.5-punk.exe with ONLY the vendored twapi 5.0b1 on auto_path (no system twapi present in that runtime) - 7 images embedded, converged on restamp. The system tclsh 9.0.3 run used its own twapi 5.2.0 - both arms of the 2026-07-26 probe now exercised for real.
  • Host-capability skip, twapi-unloadable condition: punkluck86 baked under the msys2 ucrt64 mingw tclsh86 (windows process family, 8.6, no zipfs) - distinct notice "embedding UNAVAILABLE ... twapi resource APIs not loadable", sidecar written, kit assembled via the zipfs-less concat path and deployed unstamped (graceful completion).
  • Runtime store originals untouched throughout: stamping operates on per-kit copies (src/build/iconed) of the extraction-phase raw prefix; store file mtimes verified unchanged after all bakes.
  • Host-capability skip, cross-host condition (the real thing, not a code read): punkluck86 baked from WSL linux (tclsh 8.6.14, /mnt/c) - distinct notice "embedding UNAVAILABLE ... cross-host: win32 target baked on a linux-x86_64 host cannot run the twapi resource APIs (portable post-hoc stamping is the G-128 remedy)", kit INSTALLED, sidecar deployed with embedding_status unavailable + that reason. The cross-baked win32 kit BOOTS on windows reading its payload (scratch copy) - a working, merely icon-less artifact: the recorded known shortcoming, live. A subsequent windows-host rebake re-embedded and the deployed sidecar flipped unavailable -> embedded, demonstrating the record's host-capability dimension end-to-end.

Acceptance evaluated 2026-07-29 against every clause: met. The flip archives this file; the cross-host shortcoming + G-128 remedy note above and the Decisions section carry the durable design record.