Compare commits

...

3 Commits

Author SHA1 Message Date
Julian Noble e1c2c952e4 G-120 achieved: flip, archive, reference sweep (std-slot recycling restored) 6 days ago
Julian Noble 3808ec0914 build outputs: libunknown 0.2.2 promoted (bootsupport + _vfscommon payload) 6 days ago
Julian Noble 5d207dbfb6 G-120 active: libunknown 0.2.2 fixes leaked static-probe interp pinning std channels 6 days ago
  1. 13
      CHANGELOG.md
  2. 4
      GOALS-archive.md
  3. 4
      GOALS.md
  4. 6
      goals/G-011-console-stderr-semantics.md
  5. 7
      goals/G-101-tcl86-kit-container-strategy.md
  6. 48
      goals/archive/G-120-scriptmode-std-channel-pin.md
  7. 2
      punkproject.toml
  8. 28
      src/bootsupport/modules/punk/args-0.12.7.tm
  9. 61
      src/bootsupport/modules/punk/libunknown-0.2.2.tm
  10. 14
      src/bootsupport/modules/punk/mod-0.1.1.tm
  11. 4
      src/bootsupport/modules/punk/repl-0.5.3.tm
  12. 61
      src/modules/punk/libunknown-0.2.2.tm
  13. 61
      src/vfs/_vfscommon.vfs/modules/punk/libunknown-0.2.2.tm

13
CHANGELOG.md

@ -5,6 +5,19 @@ 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` Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy. "Project Versioning" section for the bump policy.
## [0.22.1] - 2026-07-25
- punk::libunknown 0.2.2 (G-120): the package-unknown static-builtin shortcircuit no longer
leaks its version-probe child interp. On kits whose runtime provides Thread (or any
required package) as a true static (e.g the tclsfe 9.1b0 runtime behind punk91), an
unversioned `package require` made the probe's `package vsatisfies` throw (it requires at
least one requirement argument), skipping the interp delete; the leaked child's std-channel
registrations then disabled Tcl's std-slot recycling process-wide - after
`chan close stderr` the slot stayed occupied and the next opened channel got a fileN name,
so `puts stderr` failed. The probe interp is now auto-named and deleted in a try/finally,
zero-requirement requires accept any provided static version, and probe errors fall
through to the normal tm search instead of aborting the require.
## [0.22.0] - 2026-07-25 ## [0.22.0] - 2026-07-25
- punk-runtime `list -remote` identity columns (G-119 follow-on; both payloads + rewrapped - punk-runtime `list -remote` identity columns (G-119 follow-on; both payloads + rewrapped

4
GOALS-archive.md

@ -166,3 +166,7 @@ Acceptance: Every item in the "Issue list" section carries a recorded user decis
### G-119 [achieved 2026-07-25] punk-runtime active-vs-default freshness verdict (ahead/behind/current) → detail: goals/archive/G-119-runtime-active-freshness.md ### G-119 [achieved 2026-07-25] punk-runtime active-vs-default freshness verdict (ahead/behind/current) → detail: goals/archive/G-119-runtime-active-freshness.md
Scope: src/scriptapps/bin/punk-runtime.ps1 + punk-runtime.bash (+ rewrapped bin/punk-runtime.cmd); bin/AGENTS.md (runtime-manager contract); src/tests/modules/punk/mix/testsuites/scriptwrap/ (roundtrip pin; behavioural fixture surface settled in the work) Scope: src/scriptapps/bin/punk-runtime.ps1 + punk-runtime.bash (+ rewrapped bin/punk-runtime.cmd); bin/AGENTS.md (runtime-manager contract); src/tests/modules/punk/mix/testsuites/scriptwrap/ (roundtrip pin; behavioural fixture surface settled in the work)
Acceptance: against a fixture punkbin server (PUNKBIN_URL override): list -remote prints explicit ahead/behind verdicts naming both revisions (behind adds fetch+use update guidance) and keeps the (= server default) annotation for current; a pre-family active (no toml, no -rN name) yields a stated no-revision-basis note or its sha1 hash-identity when it value-matches a server artifact; a no-name fetch prints a one-line active-vs-default note on divergence; both payloads deliver identical verdict lines (fixture surface settled: shell/testsuites/binscripts/runtimecmd_freshness.test + testsupport/httpfixture.tcl); the runtimecmd roundtrip pin stays green; bin/AGENTS.md's runtime-manager contract records the new surface. Acceptance: against a fixture punkbin server (PUNKBIN_URL override): list -remote prints explicit ahead/behind verdicts naming both revisions (behind adds fetch+use update guidance) and keeps the (= server default) annotation for current; a pre-family active (no toml, no -rN name) yields a stated no-revision-basis note or its sha1 hash-identity when it value-matches a server artifact; a no-name fetch prints a one-line active-vs-default note on divergence; both payloads deliver identical verdict lines (fixture surface settled: shell/testsuites/binscripts/runtimecmd_freshness.test + testsupport/httpfixture.tcl); the runtimecmd roundtrip pin stays green; bin/AGENTS.md's runtime-manager contract records the new surface.
### G-120 [achieved 2026-07-25] 9.1-kit script-mode boot pins process std channels: find holder, restore std-slot recycling → detail: goals/archive/G-120-scriptmode-std-channel-pin.md
Scope: src/modules/punk/console-999999.0a1.0.tm; src/modules/punk/repl-999999.0a1.0.tm; src/lib/app-punkscript/; src/vfs/_config/punk_main.tcl
Acceptance: the registration holder is named (module, proc, mechanism) with reproduction evidence; the disposition is recorded here (deliberate-and-documented, or fixed); a script-mode run of the recipe `chan close stderr; open NUL w` on the current 9.1 kit yields a channel auto-named stderr with working puts stderr, and the console + repl module suites (src/tests/modules/punk/console/, src/tests/modules/punk/repl/) pass.

4
GOALS.md

@ -401,7 +401,3 @@ Detail: goals/G-115-declarative-vfs-composition.md
### G-116 [proposed] Suite-built tcltls with a zig-built crypto backend: bi-family battery, prebuilt replacement path ### G-116 [proposed] Suite-built tcltls with a zig-built crypto backend: bi-family battery, prebuilt replacement path
Scope: src/buildsuites/suite_tcl90/ (build_tcltls module + crypto-backend build, sources.config + build.zig.zon source records, kit-family bi payload + metadata extension, test_gate record step); src/vfs kit payloads carrying vendored tcltls binaries (current-state reference + recorded disposition only - removal stays G-004-era work); punkbin (as eventual artifact destination via the G-067 library class); consumers punk::imap4 / punk::netbox (package require tls) as verification context Scope: src/buildsuites/suite_tcl90/ (build_tcltls module + crypto-backend build, sources.config + build.zig.zon source records, kit-family bi payload + metadata extension, test_gate record step); src/vfs kit payloads carrying vendored tcltls binaries (current-state reference + recorded disposition only - removal stays G-004-era work); punkbin (as eventual artifact destination via the G-067 library class); consumers punk::imap4 / punk::netbox (package require tls) as verification context
Detail: goals/G-116-suite-built-tcltls.md Detail: goals/G-116-suite-built-tcltls.md
### G-120 [proposed] 9.1-kit script-mode boot pins process std channels: find holder, restore std-slot recycling
Scope: src/modules/punk/console-999999.0a1.0.tm; src/modules/punk/repl-999999.0a1.0.tm; src/lib/app-punkscript/; src/vfs/_config/punk_main.tcl
Detail: goals/G-120-scriptmode-std-channel-pin.md

6
goals/G-011-console-stderr-semantics.md

@ -106,4 +106,10 @@ untouched.
- **Not in scope.** Channel transforms/filtering on err (shellfilter stacks), a separate err - **Not in scope.** Channel transforms/filtering on err (shellfilter stacks), a separate err
reader, and any change to shellrun's process-layer stdout/stderr handling - this goal defines reader, and any change to shellrun's process-layer stdout/stderr handling - this goal defines
the console-side slot they can target. the console-side slot they can target.
- G-120 (archived) restored std-slot recycling on the 9.1 kit (a leaked
punk::libunknown static-probe child interp was pinning the process std
channels; fixed in libunknown 0.2.2) - a script-mode close/rebind of stderr
now works on all current kits, so a recycled rebound stderr is a viable
consumer of this goal's defined routing - see
goals/archive/G-120-scriptmode-std-channel-pin.md.
- Archived-goal references in this file: G-001 achieved 2026-07-11 (goals/archive/G-001-pluggable-console-backends.md);G-007 achieved 2026-07-05 (goals/archive/G-007-console-location-transparency.md). - Archived-goal references in this file: G-001 achieved 2026-07-11 (goals/archive/G-001-pluggable-console-backends.md);G-007 achieved 2026-07-05 (goals/archive/G-007-console-location-transparency.md).

7
goals/G-101-tcl86-kit-container-strategy.md

@ -74,8 +74,11 @@ wrap), and only worth activating once G-099/G-100 land.
terminals; (2) in-place FreeConsole + close/open-NUL std reassignment works on terminals; (2) in-place FreeConsole + close/open-NUL std reassignment works on
8.6 and on stock 9.x + 9.0.x-era punk kits (Tcl installs the next created channel 8.6 and on stock 9.x + 9.0.x-era punk kits (Tcl installs the next created channel
into the vacated std slot - tclIO.c Tcl_CreateChannel; recipe `chan close stderr; into the vacated std slot - tclIO.c Tcl_CreateChannel; recipe `chan close stderr;
open NUL w` yields a channel auto-named stderr), but is currently blocked on the open NUL w` yields a channel auto-named stderr), and since the 9.1-kit std-slot
9.1 kit by a boot-layer std-slot pin (investigation: G-120); (3) relaunch-self via pin was fixed (G-120, achieved 2026-07-25 - a leaked punk::libunknown
static-probe child interp held the std channels; libunknown 0.2.2 - see
goals/archive/G-120-scriptmode-std-channel-pin.md) it works on the current 9.1
kit too, so in-place stdio repair is available across current kits; (3) relaunch-self via
twapi::create_process -noconsole is the primary cross-generation mechanism (hidden twapi::create_process -noconsole is the primary cross-generation mechanism (hidden
console, stdio + package require Tk verified working, no channel surgery; the console, stdio + package require Tk verified working, no channel surgery; the
brief console flash is OS-unavoidable for console-subsystem exes; sign release brief console flash is OS-unavoidable for console-subsystem exes; sign release

48
goals/G-120-scriptmode-std-channel-pin.md → goals/archive/G-120-scriptmode-std-channel-pin.md

@ -1,6 +1,6 @@
# G-120 9.1-kit script-mode boot pins process std channels: find holder, restore std-slot recycling # G-120 9.1-kit script-mode boot pins process std channels: find holder, restore std-slot recycling
Status: proposed Status: achieved 2026-07-25
Scope: src/modules/punk/console-999999.0a1.0.tm; src/modules/punk/repl-999999.0a1.0.tm; src/lib/app-punkscript/; src/vfs/_config/punk_main.tcl Scope: src/modules/punk/console-999999.0a1.0.tm; src/modules/punk/repl-999999.0a1.0.tm; src/lib/app-punkscript/; src/vfs/_config/punk_main.tcl
Goal: identify what in the 9.1-kit script-mode boot path retains a registration on the process std channels (keeping their std slots occupied and their OS handles alive across an explicit chan close), determine whether that retention is deliberate console-ownership machinery or a leak, and land the recorded disposition so that closing a std channel frees its slot - restoring Tcl's std-channel recycling - without breaking console abstraction behaviour. Goal: identify what in the 9.1-kit script-mode boot path retains a registration on the process std channels (keeping their std slots occupied and their OS handles alive across an explicit chan close), determine whether that retention is deliberate console-ownership machinery or a leak, and land the recorded disposition so that closing a std channel frees its slot - restoring Tcl's std-channel recycling - without breaking console abstraction behaviour.
Acceptance: the registration holder is named (module, proc, mechanism) with reproduction evidence; the disposition is recorded here (deliberate-and-documented, or fixed); a script-mode run of the recipe `chan close stderr; open NUL w` on the current 9.1 kit yields a channel auto-named stderr with working puts stderr, and the console + repl module suites (src/tests/modules/punk/console/, src/tests/modules/punk/repl/) pass. Acceptance: the registration holder is named (module, proc, mechanism) with reproduction evidence; the disposition is recorded here (deliberate-and-documented, or fixed); a script-mode run of the recipe `chan close stderr; open NUL w` on the current 9.1 kit yields a channel auto-named stderr with working puts stderr, and the console + repl module suites (src/tests/modules/punk/console/, src/tests/modules/punk/repl/) pass.
@ -46,6 +46,48 @@ by absence: strip the suspected stage and re-run the recipe.
## Notes ## Notes
- HOLDER (named 2026-07-25): punk::libunknown 0.2.1, proc
`zipfs_tm_UnknownHandler`, builtin-static shortcircuit block - the static
version-probe child interp (fixed name `ptest`) leaked when
`package vsatisfies $static_version {*}$args` threw `wrong # args` on an
UNVERSIONED require (zero requirement args) between `interp create` and
`interp delete`. A non-safe child interp holds registrations on the process
std channels (Tcl shares stdin/stdout/stderr into it at creation), so the
leaked child kept the closed stderr's OS handle alive and its std slot
occupied - Tcl_CreateChannel's std-slot install never fires.
- Reproduction evidence (2026-07-25, bin/punk91.exe 9.1b0 script mode):
`interp children` -> `ptest` with `chan names` = stderr stdin stdout and
`info loaded {}` = {{} Thread}; recipe yielded `file216195028c0` and
"can not find channel named stderr". Same-process `interp delete ptest`
then recipe -> channel auto-named stderr, puts stderr OK (causality proven).
Post-boot re-trigger: `package forget thread; package require thread`
recreated the leak on demand (`package require dde` likewise, while
SUCCEEDING via the fallback path); step-through isolated
`package vsatisfies 3.0.5` (zero requirements) as the throwing call -
identical arity error on 9.0.5 and 9.1b0, so the punk91-only symptom is
runtime-payload-driven: tclsfe 9.1b0 provides Thread as a true static
(`{}` prefix in `info loaded`, hit by boot's unversioned require), while
punk905 loads Thread from a dll path (`//zipfs:/app/bin/tcl9thread301.dll`)
so the shortcircuit never fires there.
- Kit verification 2026-07-25 (achieved flip evidence): fix promoted through the
gates (make.tcl modules -> bootsupport -> vfscommonupdate -confirm 0, outputs
committed) and kits rebaked (make.tcl vfs, 8 kits installed). Plain-mode
`bin/punk91 script` recipe on the rebuilt kit: no child interps after boot,
`chan close stderr; open NUL w` yields a channel auto-named stderr,
puts stderr OK. punk905 recipe remains green. Console + repl + libunknown
suites: 12 files, 105/105 pass (native tclsh90, Tcl 9.0.3). Scope note:
the fix landed in punk::libunknown (loaded by the boot path) rather than the
Scope-listed console/repl/app-punkscript surfaces - those were the
instrumented suspects, all cleared by the investigation.
- DISPOSITION: leak (bug), FIXED - not deliberate console-ownership machinery
(the G-007 ownership registry was not involved; the Context ruled-out list
stands). Fix = punk::libunknown 0.2.2: auto-named probe interp deleted in a
try/finally, zero-requirement requires accept any provided static version,
probe errors fall through to the normal tm search instead of aborting the
require. Verified under `bin/punk91 src script`: no child interp after boot,
recipe yields a channel auto-named stderr with working puts stderr. Suites:
libunknown + console + repl (12 files, 105 tests) all pass under native
tclsh90 (Tcl 9.0.3).
- Related: G-118 (achieved 2026-07-25 - see goals/archive/G-118-tclsh-subcommand-review.md; - Related: G-118 (achieved 2026-07-25 - see goals/archive/G-118-tclsh-subcommand-review.md;
console-path machinery + piperepl residue territory; its Notes console-path machinery + piperepl residue territory; its Notes
record punk91's runtime as unpatched, ruling out the patch as the holder); G-011 record punk91's runtime as unpatched, ruling out the patch as the holder); G-011
@ -54,6 +96,10 @@ by absence: strip the suspected stage and re-run the recipe.
channel-lifetime territory); archived G-007 channel-lifetime territory); archived G-007
(goals/archive/G-007-console-location-transparency.md - the console ownership (goals/archive/G-007-console-location-transparency.md - the console ownership
registry lineage the holder most likely belongs to). registry lineage the holder most likely belongs to).
- Activation freshness 2026-07-25: goals_xref.tcl score G-120 re-run at the
proposed->active flip - no relationships beyond the drafting survey below (top
unlinked hits G-013/G-044/G-008/G-043 are the already-dismissed repl-feature
module-path sharers; no goals drafted in the interval).
- Overlap survey 2026-07-25 (goals_xref.tcl paths over the scope): surfaced - Overlap survey 2026-07-25 (goals_xref.tcl paths over the scope): surfaced
G-002/003/008/009/010/011/013/014/034/038/042/043/044 (punk/repl + punk/console G-002/003/008/009/010/011/013/014/034/038/042/043/044 (punk/repl + punk/console
module-path sharing - repl-feature goals, no substantive pin relationship), G-077 module-path sharing - repl-feature goals, no substantive pin relationship), G-077

2
punkproject.toml

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

28
src/bootsupport/modules/punk/args-0.12.6.tm → src/bootsupport/modules/punk/args-0.12.7.tm

@ -8,7 +8,7 @@
# (C) 2024 # (C) 2024
# #
# @@ Meta Begin # @@ Meta Begin
# Application punk::args 0.12.6 # Application punk::args 0.12.7
# Meta platform tcl # Meta platform tcl
# Meta license <unspecified> # Meta license <unspecified>
# @@ Meta End # @@ Meta End
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin punkshell_module_punk::args 0 0.12.6] #[manpage_begin punkshell_module_punk::args 0 0.12.7]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {args parsing}] [comment {-- Name section and table of contents description --}] #[titledesc {args parsing}] [comment {-- Name section and table of contents description --}]
#[moddesc {args to nested dict of opts and values}] [comment {-- Description at end of page heading --}] #[moddesc {args to nested dict of opts and values}] [comment {-- Description at end of page heading --}]
@ -4765,8 +4765,26 @@ tcl::namespace::eval punk::args {
} else { } else {
set needed [list] set needed [list]
foreach pkgns $nslist { foreach pkgns $nslist {
if {[string match (autodef)* $pkgns]} { if {[string match {(*)*} $pkgns]} {
set pkgns [string range $pkgns 9 end] #tag-prefixed id 'namespace' e.g (autodef)::myensemble, or a
#script-level id family e.g (script)::punkboot, (script)::punkexe.
#Not a real namespace: the scan records documenting packages under
#the TAGGED key (namespace qualifiers of the tagged id), so consult
#namespace_docpackages with the original key first, then strip the
#tag for the real-namespace probes below ((autodef) ids also map to
#a real namespace; a bare tag or tag-only qualifier has nothing more
#to probe).
if {[dict exists $namespace_docpackages $pkgns]} {
foreach docns [dict get $namespace_docpackages $pkgns] {
if {$docns ni $loaded_packages && $docns ni $needed} {
lappend needed $docns
}
}
}
set pkgns [string range $pkgns [expr {[string first ) $pkgns]+1}] end]
if {$pkgns eq ""} {
continue
}
} }
if {![string match ::* $pkgns]} { if {![string match ::* $pkgns]} {
puts stderr "warning: update_definitions received unqualified ns: $pkgns" puts stderr "warning: update_definitions received unqualified ns: $pkgns"
@ -14071,7 +14089,7 @@ package provide punk::args [tcl::namespace::eval punk::args {
tcl::namespace::path {::punk::args::lib ::punk::args::system} tcl::namespace::path {::punk::args::lib ::punk::args::system}
variable pkg punk::args variable pkg punk::args
variable version variable version
set version 0.12.6 set version 0.12.7
}] }]
return return

61
src/modules/punk/libunknown-0.2.1.tm → src/bootsupport/modules/punk/libunknown-0.2.2.tm

@ -7,7 +7,7 @@
# (C) 2025 # (C) 2025
# #
# @@ Meta Begin # @@ Meta Begin
# Application punk::libunknown 0.2.1 # Application punk::libunknown 0.2.2
# Meta platform tcl # Meta platform tcl
# Meta license MIT # Meta license MIT
# @@ Meta End # @@ Meta End
@ -18,6 +18,19 @@
# from src/modules/AGENTS.md "Versioning And Releases" - bumping means # from src/modules/AGENTS.md "Versioning And Releases" - bumping means
# renaming the file AND updating the Meta line above, the manpage_begin line # renaming the file AND updating the Meta line above, the manpage_begin line
# below and the provide-block version, then appending a line here): # below and the provide-block version, then appending a line here):
#0.2.2 - zipfs_tm_UnknownHandler: the builtin-static shortcircuit probe interp no
# longer leaks (G-120). The probe child interp was created under the fixed
# name ptest and deleted only on the non-error paths; an unversioned
# 'package require foo' of a static package made the version check
# 'package vsatisfies $v' throw (wrong # args - vsatisfies needs >=1
# requirement), skipping the delete. The leaked child held registrations
# on the process std channels (Tcl shares them into non-safe child
# interps), so an explicit 'chan close stderr' left the OS handle alive
# and the std slot occupied - the next opened channel got a fileN name
# instead of recycling into stderr. Now: auto-named probe interp, deleted
# in a try/finally; zero-requirement requires treat any provided version
# as satisfying; probe errors fall through to the normal tm search
# instead of aborting the require.
#0.2.1 - zipfs_tm_UnknownHandler: the zipfs static-path can_skip_update state is #0.2.1 - zipfs_tm_UnknownHandler: the zipfs static-path can_skip_update state is
# written keyed by the SPECIFIC searchpath actually scanned (tm path + # written keyed by the SPECIFIC searchpath actually scanned (tm path +
# package namespace subdir) to match its reader and libunknown::init's # package namespace subdir) to match its reader and libunknown::init's
@ -42,7 +55,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin punkshell_module_punk::libunknown 0 0.2.1] #[manpage_begin punkshell_module_punk::libunknown 0 0.2.2]
#[copyright "2025"] #[copyright "2025"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] #[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}] #[moddesc {-}] [comment {-- Description at end of page heading --}]
@ -190,22 +203,36 @@ tcl::namespace::eval ::punk::libunknown {
set loaded [lsearch -inline -index 1 -nocase [info loaded] $name] set loaded [lsearch -inline -index 1 -nocase [info loaded] $name]
if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} { if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} {
lassign $loaded _ cased_name lassign $loaded _ cased_name
interp create ptest #Version-probe the static package in a throwaway child interp.
ptest eval [list load {} $cased_name] #The child must never outlive this block: a (non-safe) child interp
set static_version [ptest eval [list package provide [string tolower $cased_name]]] #holds registrations on the process std channels, so a leaked probe
set pname [string tolower $cased_name] #interp disables Tcl's std-slot recycling process-wide (after
if {$static_version eq ""} { #'chan close stderr' the OS handle stays alive and the slot stays
set static_version [ptest eval [list package provide $cased_name]] #occupied, so the next opened channel cannot become stderr) - G-120.
set pname $cased_name set probeinterp [interp create]
} try {
if {$static_version ne ""} { interp eval $probeinterp [list load {} $cased_name]
if {[package vsatisfies $static_version {*}$args]} { set static_version [interp eval $probeinterp [list package provide [string tolower $cased_name]]]
package ifneeded $pname $static_version [list load {} $cased_name] set pname [string tolower $cased_name]
interp delete ptest if {$static_version eq ""} {
return set static_version [interp eval $probeinterp [list package provide $cased_name]]
set pname $cased_name
}
if {$static_version ne ""} {
#zero requirements (plain 'package require foo') accepts any
#provided version; package vsatisfies errors without at least
#one requirement argument.
if {![llength $args] || [package vsatisfies $static_version {*}$args]} {
package ifneeded $pname $static_version [list load {} $cased_name]
return
}
} }
} on error {} {
#probe failure (e.g a static whose init needs more than a bare
#child interp) - not fatal: fall through to the normal tm search.
} finally {
interp delete $probeinterp
} }
interp delete ptest
} }
#------------------------------ #------------------------------
@ -2130,7 +2157,7 @@ namespace eval ::punk::args::register {
package provide punk::libunknown [tcl::namespace::eval ::punk::libunknown { package provide punk::libunknown [tcl::namespace::eval ::punk::libunknown {
variable pkg punk::libunknown variable pkg punk::libunknown
variable version variable version
set version 0.2.1 set version 0.2.2
}] }]
return return

14
src/bootsupport/modules/punk/mod-0.1.1.tm

@ -71,12 +71,16 @@ namespace eval punk::mod::cli {
} }
} }
dict set appinfo versions $versions dict set appinfo versions $versions
#todo - natsort! #version-aware selection (2026-07-22 audit): was a plain lexical lsort
set sorted_versions [lsort $versions] #(1.10 sorts before 1.9) with lindex 0 - the LOWEST - as 'latest'.
set latest [lindex $sorted_versions 0] #Empty version (unversioned main) is excluded from comparison (package
if {$latest eq "" && [llength $sorted_versions] > 1} { #vcompare errors on empty operands); latest = highest by vcompare.
set latest [lindex $sorted_versions 1] set realversions {}
foreach v $versions {
if {$v ne ""} {lappend realversions $v}
} }
set sorted_versions [lsort -command {package vcompare} $realversions]
set latest [lindex $sorted_versions end] ;#"" when only an unversioned main exists
dict set appinfo latest $latest dict set appinfo latest $latest
dict set appinfo bases $bases dict set appinfo bases $bases

4
src/bootsupport/modules/punk/repl-0.5.2.tm → src/bootsupport/modules/punk/repl-0.5.3.tm

@ -4290,6 +4290,7 @@ namespace eval repl {
package require punk::lib package require punk::lib
package require punk::args package require punk::args
catch {package require punk::args::moduledoc::tclcore} ;#while tclcore is highly desirable, and should be installed with punk::args - it's not critical catch {package require punk::args::moduledoc::tclcore} ;#while tclcore is highly desirable, and should be installed with punk::args - it's not critical
catch {package require punk::args::moduledoc::punkexe} ;#launch subcommand family documentation (G-118) - not critical
package require textblock package require textblock
} }
@ -4470,6 +4471,7 @@ namespace eval repl {
catch {package require punk::args::moduledoc::parser} catch {package require punk::args::moduledoc::parser}
#----------- #-----------
catch {package require punk::args::moduledoc::tclcore} ;#while tclcore is highly desirable, and should be installed with punk::args - it's not critical catch {package require punk::args::moduledoc::tclcore} ;#while tclcore is highly desirable, and should be installed with punk::args - it's not critical
catch {package require punk::args::moduledoc::punkexe} ;#launch subcommand family documentation (G-118) - not critical
} errM]} { } errM]} {
puts stderr "========================" puts stderr "========================"
puts stderr "code interp error:" puts stderr "code interp error:"
@ -4565,7 +4567,7 @@ namespace eval ::punk::args::register {
package provide punk::repl [namespace eval punk::repl { package provide punk::repl [namespace eval punk::repl {
variable version variable version
set version 0.5.2 set version 0.5.3
}] }]
#repl::start $program_read_stdin_pipe #repl::start $program_read_stdin_pipe

61
src/vfs/_vfscommon.vfs/modules/punk/libunknown-0.2.1.tm → src/modules/punk/libunknown-0.2.2.tm

@ -7,7 +7,7 @@
# (C) 2025 # (C) 2025
# #
# @@ Meta Begin # @@ Meta Begin
# Application punk::libunknown 0.2.1 # Application punk::libunknown 0.2.2
# Meta platform tcl # Meta platform tcl
# Meta license MIT # Meta license MIT
# @@ Meta End # @@ Meta End
@ -18,6 +18,19 @@
# from src/modules/AGENTS.md "Versioning And Releases" - bumping means # from src/modules/AGENTS.md "Versioning And Releases" - bumping means
# renaming the file AND updating the Meta line above, the manpage_begin line # renaming the file AND updating the Meta line above, the manpage_begin line
# below and the provide-block version, then appending a line here): # below and the provide-block version, then appending a line here):
#0.2.2 - zipfs_tm_UnknownHandler: the builtin-static shortcircuit probe interp no
# longer leaks (G-120). The probe child interp was created under the fixed
# name ptest and deleted only on the non-error paths; an unversioned
# 'package require foo' of a static package made the version check
# 'package vsatisfies $v' throw (wrong # args - vsatisfies needs >=1
# requirement), skipping the delete. The leaked child held registrations
# on the process std channels (Tcl shares them into non-safe child
# interps), so an explicit 'chan close stderr' left the OS handle alive
# and the std slot occupied - the next opened channel got a fileN name
# instead of recycling into stderr. Now: auto-named probe interp, deleted
# in a try/finally; zero-requirement requires treat any provided version
# as satisfying; probe errors fall through to the normal tm search
# instead of aborting the require.
#0.2.1 - zipfs_tm_UnknownHandler: the zipfs static-path can_skip_update state is #0.2.1 - zipfs_tm_UnknownHandler: the zipfs static-path can_skip_update state is
# written keyed by the SPECIFIC searchpath actually scanned (tm path + # written keyed by the SPECIFIC searchpath actually scanned (tm path +
# package namespace subdir) to match its reader and libunknown::init's # package namespace subdir) to match its reader and libunknown::init's
@ -42,7 +55,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin punkshell_module_punk::libunknown 0 0.2.1] #[manpage_begin punkshell_module_punk::libunknown 0 0.2.2]
#[copyright "2025"] #[copyright "2025"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] #[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}] #[moddesc {-}] [comment {-- Description at end of page heading --}]
@ -190,22 +203,36 @@ tcl::namespace::eval ::punk::libunknown {
set loaded [lsearch -inline -index 1 -nocase [info loaded] $name] set loaded [lsearch -inline -index 1 -nocase [info loaded] $name]
if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} { if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} {
lassign $loaded _ cased_name lassign $loaded _ cased_name
interp create ptest #Version-probe the static package in a throwaway child interp.
ptest eval [list load {} $cased_name] #The child must never outlive this block: a (non-safe) child interp
set static_version [ptest eval [list package provide [string tolower $cased_name]]] #holds registrations on the process std channels, so a leaked probe
set pname [string tolower $cased_name] #interp disables Tcl's std-slot recycling process-wide (after
if {$static_version eq ""} { #'chan close stderr' the OS handle stays alive and the slot stays
set static_version [ptest eval [list package provide $cased_name]] #occupied, so the next opened channel cannot become stderr) - G-120.
set pname $cased_name set probeinterp [interp create]
} try {
if {$static_version ne ""} { interp eval $probeinterp [list load {} $cased_name]
if {[package vsatisfies $static_version {*}$args]} { set static_version [interp eval $probeinterp [list package provide [string tolower $cased_name]]]
package ifneeded $pname $static_version [list load {} $cased_name] set pname [string tolower $cased_name]
interp delete ptest if {$static_version eq ""} {
return set static_version [interp eval $probeinterp [list package provide $cased_name]]
set pname $cased_name
}
if {$static_version ne ""} {
#zero requirements (plain 'package require foo') accepts any
#provided version; package vsatisfies errors without at least
#one requirement argument.
if {![llength $args] || [package vsatisfies $static_version {*}$args]} {
package ifneeded $pname $static_version [list load {} $cased_name]
return
}
} }
} on error {} {
#probe failure (e.g a static whose init needs more than a bare
#child interp) - not fatal: fall through to the normal tm search.
} finally {
interp delete $probeinterp
} }
interp delete ptest
} }
#------------------------------ #------------------------------
@ -2130,7 +2157,7 @@ namespace eval ::punk::args::register {
package provide punk::libunknown [tcl::namespace::eval ::punk::libunknown { package provide punk::libunknown [tcl::namespace::eval ::punk::libunknown {
variable pkg punk::libunknown variable pkg punk::libunknown
variable version variable version
set version 0.2.1 set version 0.2.2
}] }]
return return

61
src/bootsupport/modules/punk/libunknown-0.2.1.tm → src/vfs/_vfscommon.vfs/modules/punk/libunknown-0.2.2.tm

@ -7,7 +7,7 @@
# (C) 2025 # (C) 2025
# #
# @@ Meta Begin # @@ Meta Begin
# Application punk::libunknown 0.2.1 # Application punk::libunknown 0.2.2
# Meta platform tcl # Meta platform tcl
# Meta license MIT # Meta license MIT
# @@ Meta End # @@ Meta End
@ -18,6 +18,19 @@
# from src/modules/AGENTS.md "Versioning And Releases" - bumping means # from src/modules/AGENTS.md "Versioning And Releases" - bumping means
# renaming the file AND updating the Meta line above, the manpage_begin line # renaming the file AND updating the Meta line above, the manpage_begin line
# below and the provide-block version, then appending a line here): # below and the provide-block version, then appending a line here):
#0.2.2 - zipfs_tm_UnknownHandler: the builtin-static shortcircuit probe interp no
# longer leaks (G-120). The probe child interp was created under the fixed
# name ptest and deleted only on the non-error paths; an unversioned
# 'package require foo' of a static package made the version check
# 'package vsatisfies $v' throw (wrong # args - vsatisfies needs >=1
# requirement), skipping the delete. The leaked child held registrations
# on the process std channels (Tcl shares them into non-safe child
# interps), so an explicit 'chan close stderr' left the OS handle alive
# and the std slot occupied - the next opened channel got a fileN name
# instead of recycling into stderr. Now: auto-named probe interp, deleted
# in a try/finally; zero-requirement requires treat any provided version
# as satisfying; probe errors fall through to the normal tm search
# instead of aborting the require.
#0.2.1 - zipfs_tm_UnknownHandler: the zipfs static-path can_skip_update state is #0.2.1 - zipfs_tm_UnknownHandler: the zipfs static-path can_skip_update state is
# written keyed by the SPECIFIC searchpath actually scanned (tm path + # written keyed by the SPECIFIC searchpath actually scanned (tm path +
# package namespace subdir) to match its reader and libunknown::init's # package namespace subdir) to match its reader and libunknown::init's
@ -42,7 +55,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin punkshell_module_punk::libunknown 0 0.2.1] #[manpage_begin punkshell_module_punk::libunknown 0 0.2.2]
#[copyright "2025"] #[copyright "2025"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}] #[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}] #[moddesc {-}] [comment {-- Description at end of page heading --}]
@ -190,22 +203,36 @@ tcl::namespace::eval ::punk::libunknown {
set loaded [lsearch -inline -index 1 -nocase [info loaded] $name] set loaded [lsearch -inline -index 1 -nocase [info loaded] $name]
if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} { if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} {
lassign $loaded _ cased_name lassign $loaded _ cased_name
interp create ptest #Version-probe the static package in a throwaway child interp.
ptest eval [list load {} $cased_name] #The child must never outlive this block: a (non-safe) child interp
set static_version [ptest eval [list package provide [string tolower $cased_name]]] #holds registrations on the process std channels, so a leaked probe
set pname [string tolower $cased_name] #interp disables Tcl's std-slot recycling process-wide (after
if {$static_version eq ""} { #'chan close stderr' the OS handle stays alive and the slot stays
set static_version [ptest eval [list package provide $cased_name]] #occupied, so the next opened channel cannot become stderr) - G-120.
set pname $cased_name set probeinterp [interp create]
} try {
if {$static_version ne ""} { interp eval $probeinterp [list load {} $cased_name]
if {[package vsatisfies $static_version {*}$args]} { set static_version [interp eval $probeinterp [list package provide [string tolower $cased_name]]]
package ifneeded $pname $static_version [list load {} $cased_name] set pname [string tolower $cased_name]
interp delete ptest if {$static_version eq ""} {
return set static_version [interp eval $probeinterp [list package provide $cased_name]]
set pname $cased_name
}
if {$static_version ne ""} {
#zero requirements (plain 'package require foo') accepts any
#provided version; package vsatisfies errors without at least
#one requirement argument.
if {![llength $args] || [package vsatisfies $static_version {*}$args]} {
package ifneeded $pname $static_version [list load {} $cased_name]
return
}
} }
} on error {} {
#probe failure (e.g a static whose init needs more than a bare
#child interp) - not fatal: fall through to the normal tm search.
} finally {
interp delete $probeinterp
} }
interp delete ptest
} }
#------------------------------ #------------------------------
@ -2130,7 +2157,7 @@ namespace eval ::punk::args::register {
package provide punk::libunknown [tcl::namespace::eval ::punk::libunknown { package provide punk::libunknown [tcl::namespace::eval ::punk::libunknown {
variable pkg punk::libunknown variable pkg punk::libunknown
variable version variable version
set version 0.2.1 set version 0.2.2
}] }]
return return
Loading…
Cancel
Save