Browse Source

punk::libunknown 0.3.0: ::package override installs via commandstack (G-176 increment 2; project 0.63.0)

The ::package override (the 'epoch' subcommand and controlled 'forget')
now installs via commandstack::rename_command -renamer punk::libunknown
instead of the former 'rename ::package :📦:' + namespace-import
mechanism. Both live ::package overrides (this and
punk::packagepreference's) now share one managed stack whatever the
install order - stack removals and third-party renames re-link correctly
instead of operating on the wrong command (previously commandstack's
::package stack believed the packagepreference wrapper was live while
libunknown's unmanaged import actually was; restore_original would have
stranded :📦:).

Migration deltas (exactly the set enumerated in the goal Context):
- the override body runs at :: - the four bare epoch_incr_pkg/epoch_incr_tm
  calls are fully qualified (the old namespace-import execution context is
  gone)
- the dead 'variable has_package_files' declaration dropped (its only use
  was already commented out; the module-load probe stays)
- the two :📦: delegation sites (forget arm, default arm) now
  delegate via COMMANDSTACKNEXT; :📦: no longer exists (nothing in
  the tree called it)
- init does 'package require commandstack' (a no-op when already provided
  - e.g whenever punk::packagepreference::install ran first, as in
  punkshell boot; resolvable via tm paths in every other real init
  context - bootsupport and the common vfs both carry commandstack-0.7.1)
- re-entry guard is now a stack query for the punk::libunknown record
  (same 'already done' notice; the old ::punk::libunknown::package
  proc-existence check tested a proc that no longer exists)
- init argdoc reworded; file renamed libunknown-0.2.4.tm ->
  libunknown-0.3.0.tm (git mv + fossil mv) with Meta/manpage/provide-block
  version updates and a changelog entry per the in-file bump procedure.

Behaviour of every subcommand arm is unchanged: all increment-1 BEHAVIOUR
pins in packageoverride.test passed byte-identically; only its four
MARKED-TO-FLIP install-shape pins flipped as fenced (origin ::package, no
:📦: residue, managed stack records - 1 solo, 2 in both
packagepreference interop orders). discovery.test and packageoverride.test
child interps now pre-source the source-tree commandstack by path (their
tm paths are cleared); libsearch/shadowing children resolve commandstack
via inherited parent paths and needed no changes.

Two out-of-scope items recorded as open Follow-ons in the goal (Scope
amendment needed if pursued): boot-site commandstack pre-sourcing
(punkboot core.tcl / make.tcl / punk::repl / punk::lib child-seeding - to
preserve the minimal-untracked epoch intent exactly) and make.tcl's
'info commands ::punk::libunknown::package' skip-guard modernization
(old kits still satisfy it; switch to a package-provide check before or
with the 0.3.0 bootsupport/kit promotion).

Goal Scope mirrors updated to the new filename (rename anticipated in the
Scope text). Project 0.62.2 -> 0.63.0 + CHANGELOG.

Suites: libunknown tree + loadedlib 42/42; full modules tree 1346 total /
1335 pass / 11 constraint-skipped / 0 fail (zig-built tclsh90s 9.0.5);
testbody_lint 1699 clean; goals_lint clean (80 active-index / 96
archived); make.tcl projectversion consistency OK (and that run itself
exercised the bootsupport-0.2.4 transition path live).

Claude-Session: https://claude.ai/code/session_01QgaxV27VZkmEec7oNbEVFc
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 3 days ago
parent
commit
a7bc62c3fb
  1. 15
      CHANGELOG.md
  2. 2
      GOALS.md
  3. 42
      goals/G-176-commandstack-doc-integration.md
  4. 2
      punkproject.toml
  5. 79
      src/modules/punk/libunknown-0.3.0.tm
  6. 2
      src/tests/modules/AGENTS.md
  7. 25
      src/tests/modules/punk/libunknown/testsuites/discovery/discovery.test
  8. 76
      src/tests/modules/punk/libunknown/testsuites/discovery/packageoverride.test

15
CHANGELOG.md

@ -5,6 +5,21 @@ 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.63.0] - 2026-08-08
- `punk::libunknown` 0.3.0 (G-176 increment 2): the `::package` override
(the `epoch` subcommand and controlled `forget`) now installs via
`commandstack::rename_command` instead of the former
`rename ::package ::package::` + namespace-import mechanism, so both live
`::package` overrides (libunknown's and punk::packagepreference's) share
one managed stack whatever the install order - stack removals and
third-party renames re-link correctly instead of operating on the wrong
command. Behaviour of every subcommand arm is unchanged (pinned by the
increment-1 characterisation suite; only its marked install-shape pins
flipped). `::package::` no longer exists (nothing in the tree called it);
libunknown's init now requires commandstack (a no-op when
packagepreference installed first, as in punkshell boot).
## [0.62.2] - 2026-08-08
- `i ::tcl::prefix` no longer emits 12x "warning: update_definitions

2
GOALS.md

@ -405,6 +405,6 @@ Scope: src/modules/punk/args-999999.0a1.0.tm (resolve @dynamic cache-skip, by-id
Detail: goals/G-175-punkargs-dynamic-resolve-multiplicity.md
### G-176 [active] commandstack doc integration - stack-tied overrider argdocs, override hints, and the libunknown package-override migration
Scope: src/modules/commandstack-999999.0a1.0.tm (rename_command -punkargs option, stackdocs pending-docs namespace, remove_rename detach hook, help/argdoc how-to); src/modules/punk/libunknown-0.2.4.tm (package-override migration to commandstack - fixed-version file, renamed on bump); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp/cmdinfo override-in-place hint); src/tests/modules/punk/libunknown/testsuites/discovery/ (override characterisation arms - discovery.test extensions or a sibling packageoverride.test); src/tests/modules/commandstack/testsuites/commandstack/commandstack.test (-punkargs lifecycle pins); src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (override-hint pins)
Scope: src/modules/commandstack-999999.0a1.0.tm (rename_command -punkargs option, stackdocs pending-docs namespace, remove_rename detach hook, help/argdoc how-to); src/modules/punk/libunknown-0.3.0.tm (package-override migration to commandstack - fixed-version file, renamed from libunknown-0.2.4.tm at increment 2); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp/cmdinfo override-in-place hint); src/tests/modules/punk/libunknown/testsuites/discovery/ (override characterisation arms - discovery.test extensions or a sibling packageoverride.test); src/tests/modules/commandstack/testsuites/commandstack/commandstack.test (-punkargs lifecycle pins); src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (override-hint pins)
Detail: goals/G-176-commandstack-doc-integration.md

42
goals/G-176-commandstack-doc-integration.md

@ -1,7 +1,7 @@
# G-176 commandstack doc integration - stack-tied overrider argdocs, override hints, and the libunknown package-override migration
Status: active
Scope: src/modules/commandstack-999999.0a1.0.tm (rename_command -punkargs option, stackdocs pending-docs namespace, remove_rename detach hook, help/argdoc how-to); src/modules/punk/libunknown-0.2.4.tm (package-override migration to commandstack - fixed-version file, renamed on bump); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp/cmdinfo override-in-place hint); src/tests/modules/punk/libunknown/testsuites/discovery/ (override characterisation arms - discovery.test extensions or a sibling packageoverride.test); src/tests/modules/commandstack/testsuites/commandstack/commandstack.test (-punkargs lifecycle pins); src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (override-hint pins)
Scope: src/modules/commandstack-999999.0a1.0.tm (rename_command -punkargs option, stackdocs pending-docs namespace, remove_rename detach hook, help/argdoc how-to); src/modules/punk/libunknown-0.3.0.tm (package-override migration to commandstack - fixed-version file, renamed from libunknown-0.2.4.tm at increment 2); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp/cmdinfo override-in-place hint); src/tests/modules/punk/libunknown/testsuites/discovery/ (override characterisation arms - discovery.test extensions or a sibling packageoverride.test); src/tests/modules/commandstack/testsuites/commandstack/commandstack.test (-punkargs lifecycle pins); src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (override-hint pins)
Goal: a command overrider using commandstack can attach punk::args doc blocks that live and die with its stack entry (supplied at rename_command time, active whether or not punk::args is loaded yet, removed by every removal path), cmdhelp renders an override-in-place hint on any commandstack-managed command's existing documentation (naming the renamers and any doc ids their records added), and the punk::libunknown ::package override - the flagship consumer, whose 'package epoch' subcommand gains real documentation this way - is migrated onto commandstack so both live ::package overrides share one managed stack.
Acceptance: characterisation tests of the CURRENT libunknown override land before any behaviour change and stay green byte-identically through the migration (the epoch tm/pkg single-arg forms, indexed <which> <index> ?keys...? queries and their error arms, the forget arm incl the tcl/Tcl/tcl::oo/tk denial and epoch-scripts pruning, abbreviation acceptance (ep/epo/epoc, fo/for/forge), a delegation pass-through pin, the re-init guard, and a packagepreference interop child covering both install orders) with only explicitly marked install-shape pins (::package import link, ::package:: parked name) flipping when the migration lands; libunknown installs its ::package override via commandstack::rename_command with a stack-query install guard mirroring punk::packagepreference and no ::package:: residue; commandstack::rename_command accepts a -punkargs list of definitionlists whose docs are queryable while the record lives - including renames performed BEFORE punk::args loads (pinned in a bare child that loads punk::args afterwards) - and are removed by remove_rename, pop_rename, remove_renamer and restore_original alike (record key appended per the key-order contract, reload-contract guard on new state); punk::ns::cmdhelp shows an override-in-place notice for a commandstack-managed command (cmdinfo carrying it as a second-axis key in the G-166 (achieved, see goals/archive/G-166-cmdinfo-unavailable-surfacing.md) style) without a hard commandstack dependency, enumerating record-attached doc ids; 'i package epoch' resolves the libunknown-attached doc in a punk shell and the mechanism how-to is described in commandstack::help and the rename_command argdoc; punk/libunknown, commandstack, punk/ns and punk/packagepreference suites pass under the canonical runtests interpreter.
@ -127,6 +127,46 @@ Increments land as separate commits, each leaving all suites green:
source the source-tree libunknown/commandstack/packagepreference by path via a
shared locator (discovery.test pattern). Tests AGENTS.md index updated.
- Increment 2 (migration) landed 2026-08-08: punk::libunknown 0.3.0 (file renamed
from libunknown-0.2.4.tm - git mv + fossil mv). The ::package override installs
via commandstack::rename_command (renamer punk::libunknown): init does
'package require commandstack' (a no-op when already provided - resolvable via
tm paths in every real init context; bootsupport and the common vfs both carry
commandstack-0.7.1) and re-entry is guarded by a stack query for our record
(same 'already done' notice). Body deltas exactly as enumerated in Context:
four epoch_incr_* calls qualified, the dead 'variable has_package_files'
declaration dropped, the two delegation sites moved to COMMANDSTACKNEXT, the
rename+import install replaced, the init argdoc reworded. All increment-1
BEHAVIOUR pins stayed green unchanged; only the marked install-shape pins
flipped (origin ::package, no ::package:: residue, managed stack records - 1
solo, 2 in both interop orders). discovery.test and packageoverride.test
children now pre-source the source-tree commandstack by path (their tm paths
are cleared); libsearch/shadowing children resolve commandstack via inherited
parent paths and needed no changes. Suites: libunknown tree + loadedlib 42/42;
full modules tree green. Project 0.63.0.
## Follow-ons
- Boot-site commandstack pre-sourcing (out of the current Scope - a Scope
amendment is needed if pursued): punkboot core.tcl, make.tcl, punk::repl's two
blocks and punk::lib's interp_sync child-seeding all pre-source LIBUNKNOWN by
path expressly to avoid triggering the old package unknown pre-init. init's
new 'package require commandstack' resolves in all of them, but when
commandstack is not yet provided it performs one ordinary unknown-path
resolution pre-init (a root-depth sibling scan lands in 'untracked' -
functional, marginally looser epoch bookkeeping; in punkshell app boot
packagepreference::install provides commandstack first so the require is a
no-op there). Extending the same glob+source technique to commandstack-*.tm at
those sites would preserve the minimal-untracked intent exactly. => open
- make.tcl guard modernization (out of the current Scope): the
'info commands ::punk::libunknown::package' check that skips re-load when
make.tcl is driven by a built punk shell tests a proc the 0.3.0 install no
longer creates. Old kits (0.2.4) still satisfy it so nothing breaks today,
but once kits carry 0.3.0 the check never fires and make.tcl re-sources
libunknown into the driving shell (harmless - init's guard no-ops - but noisy
and wasteful). Switch it to a "[package provide punk::libunknown] ne {}"
check before or with the bootsupport/kit promotion of 0.3.0. => open
## Notes
- Same-id shadowing: one live record per doc id is the documented constraint

2
punkproject.toml

@ -1,6 +1,6 @@
[project]
name = "punkshell"
version = "0.62.2"
version = "0.63.0"
license = "BSD-2-Clause"
url = "https://www.gitea1.intx.com.au/jn/punkshell"
#packager: declared identity for published artifacts (declarative, not proof -

79
src/modules/punk/libunknown-0.2.4.tm → src/modules/punk/libunknown-0.3.0.tm

@ -7,7 +7,7 @@
# (C) 2025
#
# @@ Meta Begin
# Application punk::libunknown 0.2.4
# Application punk::libunknown 0.3.0
# Meta platform tcl
# Meta license MIT
# @@ Meta End
@ -18,6 +18,23 @@
# from src/modules/AGENTS.md "Versioning And Releases" - bumping means
# renaming the file AND updating the Meta line above, the manpage_begin line
# below and the provide-block version, then appending a line here):
#0.3.0 - G-176 increment 2: the ::package override installs via
# commandstack::rename_command (renamer punk::libunknown) instead of the
# former 'rename ::package ::package::' + namespace-import mechanism, so
# both live ::package overrides (this + punk::packagepreference's) share
# one managed stack whatever the install order. init now does
# 'package require commandstack' (a no-op where commandstack is already
# provided, e.g whenever punk::packagepreference::install ran first) and
# re-entry is guarded by a stack query for our renamer record instead of
# the old ::punk::libunknown::package proc-existence check. The override
# body runs at :: (namespace-relative epoch_incr_* calls fully
# qualified; the dead 'variable has_package_files' declaration dropped -
# its only use was already commented out) and delegates via
# COMMANDSTACKNEXT (no ::package:: parking name; nothing in the tree
# called it). Behaviour of every arm is unchanged - pinned by
# tests/modules/punk/libunknown testsuites/discovery/packageoverride.test
# (the increment-1 characterisation; only its marked install-shape pins
# flipped with this change).
#0.2.4 - register_all_tm: skip the G-155 workdirs _mint and _bake alongside
# legacy _build, in both the filesystem directory walk and the tm-file
# filter (the latter also guards the zipfs whole-tree listing) - loose
@ -71,7 +88,7 @@
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::libunknown 0 0.2.4]
#[manpage_begin punkshell_module_punk::libunknown 0 0.3.0]
#[copyright "2025"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
@ -1509,10 +1526,12 @@ tcl::namespace::eval ::punk::libunknown {
-summary\
"Install punk::libunknown's custom package unknown handlers and epoch system in the current interp."\
-help\
"Renames the builtin ::package to ::package:: and installs a custom ::package proc
that intercepts 'forget' (controlled forgetting) and adds an 'epoch' subcommand
(for introspection). Sets package unknown to a two-handler chain:
zipfs_tm_UnknownHandler (tm paths) and zipfs_tclPkgUnknown (auto_path/pkgIndex).
"Installs a ::package override via commandstack::rename_command (renamer
punk::libunknown) that intercepts 'forget' (controlled forgetting) and adds an
'epoch' subcommand (for introspection); all other subcommands delegate to the
previous implementation on the commandstack. Sets package unknown to a
two-handler chain: zipfs_tm_UnknownHandler (tm paths) and zipfs_tclPkgUnknown
(auto_path/pkgIndex).
Installs traces on ::auto_path and ::tcl::tm::paths that increment the epoch
when these change, invalidating stale scan cache entries.
@ -1571,14 +1590,18 @@ tcl::namespace::eval ::punk::libunknown {
puts stderr "punk::libunknown::init - init while empty/unreadable tcl::tm::list and empty/unreadable ::auto_path"
}
if {[info commands ::punk::libunknown::package] ne ""} {
puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]"
return
#commandstack is the install mechanism for the ::package override (G-176).
#A no-op when already provided (e.g whenever punk::packagepreference::install
#ran first, or a boot context pre-sourced it); otherwise resolves via the
#standard package machinery - commandstack is lean and dependency-free.
package require commandstack
#re-entry guard: our override record on the ::package commandstack
foreach stackrecord [commandstack::get_stack ::package] {
if {[dict get $stackrecord renamer] eq "punk::libunknown"} {
puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]"
return
}
}
#if {[namespace origin ::package] eq "::punk::libunknown::package"} {
# #This is far from conclusive - there may be other renamers (e.g commandstack)
# return
#}
@ -1686,13 +1709,14 @@ tcl::namespace::eval ::punk::libunknown {
trace add variable ::auto_path write ::punk::libunknown::epoch_incr_pkg
trace add variable ::tcl::tm::paths write ::punk::libunknown::epoch_incr_tm
#set stackrecord [commandstack::rename_command -renamer punk::libunknown package {args} {
# #::package override installed by punk::libunknown::init
#}
proc package args {
#::package override - installed via commandstack (G-176) so both live
#::package overrides (this + punk::packagepreference's) share one managed
#stack whatever the install order. The body runs at :: - namespace-relative
#references must be fully qualified.
set stackrecord [commandstack::rename_command -renamer punk::libunknown package {args} {
#::package override installed by punk::libunknown::init (epoch/forget arms; all else delegates)
switch -- [lindex $args 0] {
fo - for - forge - forget {
variable has_package_files
#experimental - silently disallow forgetting things that didn't involve sourcing files
#What about static libs that also sourced files?
#packages loaded by c extensions?
@ -1731,7 +1755,7 @@ tcl::namespace::eval ::punk::libunknown {
}
}
if {[llength $ok_forgets]} {
return [uplevel 1 [list ::package:: forget {*}$ok_forgets]]
return [uplevel 1 [list $COMMANDSTACKNEXT forget {*}$ok_forgets]]
} else {
return
}
@ -1756,8 +1780,8 @@ tcl::namespace::eval ::punk::libunknown {
return [dict create $cur [dict get $epoch pkg epochs $cur]]
}
incr {
epoch_incr_pkg
epoch_incr_tm
::punk::libunknown::epoch_incr_pkg
::punk::libunknown::epoch_incr_tm
}
default {
error "package epoch [lindex $epoch_args 0] unsupported - known options: tm pkg incr"
@ -1768,10 +1792,10 @@ tcl::namespace::eval ::punk::libunknown {
set a2 [list [lindex $epoch_args 0] [lindex $epoch_args 1]]
switch -- $a2 {
{pkg incr} - {incr pkg} {
epoch_incr_pkg
::punk::libunknown::epoch_incr_pkg
}
{tm incr} - {incr tm} {
epoch_incr_tm
::punk::libunknown::epoch_incr_tm
}
default {
set which [lindex $epoch_args 0]
@ -1813,13 +1837,10 @@ tcl::namespace::eval ::punk::libunknown {
}
}
default {
return [uplevel 1 [list ::package:: {*}$args]]
return [uplevel 1 [list $COMMANDSTACKNEXT {*}$args]]
}
}
}
rename ::package ::package::
#all lowercase procs already exported from ::punk::libunknown
namespace eval :: [list ::namespace import ::punk::libunknown::package]
}]
#if {[info commands ::tcl::zipfs::root] ne ""} {
# set has_zipfs_tm 0
@ -2183,7 +2204,7 @@ namespace eval ::punk::args::register {
package provide punk::libunknown [tcl::namespace::eval ::punk::libunknown {
variable pkg punk::libunknown
variable version
set version 0.2.4
set version 0.3.0
}]
return

2
src/tests/modules/AGENTS.md

@ -51,5 +51,5 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/`
- `punk/lib/` — punk::lib tests (`testsuites/lib/`): range/index/parse/compat/interp_sync utilities, G-058 static-baseline seeding (`staticseed.test`: interp_sync_package_paths/snapshot_package_paths propagate a simulated ::punkboot static baseline and seed `load {} <prefix>` ifneeded mappings; no-op without a baseline), and the repl command-completeness engine (`commandcomplete.test`: punk::lib::system::incomplete pending-opener stacks - the info-complete quoting quirk progression (`set x "{*}{"` standalone vs in-proc-body), single openers, tabs, escapes, incomplete<->info-complete parity property; pre-repl-refactor characterization, see goals/G-044 detail preserve-list)
- `punk/packagepreference/` — punk::packagepreference tests (`testsuites/packagepreference/`): G-058 static-vs-bundled policy (`staticpolicy.test`: require of a baseline package triggers the index scan before resolution so a newer bundled copy wins, static beats older bundled, exact requires of bundled versions stay reachable, missing static mappings get seeded), and the install re-install guard (`installguard.test`, G-160: second install returns 0 silently via the record `renamer` key - the historical `rename` read threw; uninstall/re-install cycle gets the next unique tokenid; fresh child interps source the SOURCE-TREE commandstack + packagepreference by path so no bootsupport/kit snapshot can answer)
- `punk/zip/` — punk::zip tests (`testsuites/zip/zipreader.test`, G-124 - the module's first tests): the dependency-free reader over the three archive shapes (bare zip, executable-prefixed with archive-relative offsets, executable-prefixed with file-relative offsets) - archive_info's offsetstyle/dataoffset derivation incl. the nozip verdict for a plain binary carrying a stray PK\5\6, members' per-entry introspection (classification, sizes, method, crc, mtime, stored attributes) and glob/exclude selection, unzip's byte-identical CRC-verified extraction incl. the >2MB streaming path and partial extraction; the named refusals (encrypted, unknown compression method, zip64, crc mismatch, path escape) each asserting that no partial output is left behind; and the mkzip->read round trip - the first coverage that what punkshell WRITES is readable. Runs entirely on stock Tcl (verified on msys2's tclsh8.6 with no zipfs, no vfs::zip and no tcllib); two zipfs-gated tests cross-check against a real zipfs mount and record the answer to the standing directory-classification question (zipfs keys on the trailing slash, not the stored permission bits - so punk::zip::mkzip directories mount as directories). The real-runtime pin self-gates on `bin/runtime/win32-x86_64/tclsh90b4_piperepl.exe`, which is untracked
- `punk/libunknown/` — .tm same-version shadowing pin-tests (`testsuites/shadowing/`): tcl::tm::add prepend rule, head-of-tm-list wins exact-version ties, version beats order, punk::libunknown parity — shipped behaviour depends on these (runtests tm ordering, punk_main package-mode precedence, G-033); mixed .tm/pkgIndex.tcl characterization is goal G-035; discovery/epoch-cache characterization (`testsuites/discovery/discovery.test`): sibling .tm registration happens only at the requested namespace depth (deeper modules invisible to 'package names' until requested), register_all_tm deep discovery (all-depths registration, per-epoch cached no-op, head-of-tm-list precedence parity), 'package epoch' command shape, trace-driven epoch increments on tm/auto_path changes, the epoch-index short-circuit (a .tm added to an already-scanned dir needs 'package epoch incr'), and the pkgIndex.tcl sourcing-scope contract (source_pkgindex, fixed 2026-07-11 - formerly a GAP pin of the global-'dir' clobber): user global 'dir' survives pkg-unknown fallthrough, index scripts see $dir and reach the real ::auto_path (tcllib extension pattern), and their stray unqualified sets no longer leak to ::; plus ::package-override characterisation (`testsuites/discovery/packageoverride.test`, G-176 increment 1, 2026-08-08 - the pre-migration safety net for the commandstack migration: epoch single-arg/indexed query forms and their error arms, the filtered forget arm incl the tcl/Tcl/tcl::oo/tk denial and the no-ifneeded filter, abbreviation acceptance (ep/fo prefixes the parked builtin rejects - a deliberate override delta), delegation pass-through, the re-init no-op guard, and MARKED-TO-FLIP install-shape pins (::package namespace-import origin, ::package:: parked implementation, commandstack::get_stack seeing only the packagepreference record in the two-override interop children covering BOTH real-world install orders - punkshell's pkgpref-first and make.tcl's libunknown-first) that flip only with the G-176 increment-2 migration commit). Suite conventions learned the hard way: child probes source the SOURCE-TREE libunknown directly by path — 'package require punk::libunknown' tie-breaks the same-version copies (src/modules vs bootsupport) by tm path order, which favours bootsupport in the testinterp and machine env paths in children; and probe scripts must avoid global variable names the handlers use (notably 'dir') — that clobbering misdirected a fixture write into the real Tcl install during test development
- `punk/libunknown/` — .tm same-version shadowing pin-tests (`testsuites/shadowing/`): tcl::tm::add prepend rule, head-of-tm-list wins exact-version ties, version beats order, punk::libunknown parity — shipped behaviour depends on these (runtests tm ordering, punk_main package-mode precedence, G-033); mixed .tm/pkgIndex.tcl characterization is goal G-035; discovery/epoch-cache characterization (`testsuites/discovery/discovery.test`): sibling .tm registration happens only at the requested namespace depth (deeper modules invisible to 'package names' until requested), register_all_tm deep discovery (all-depths registration, per-epoch cached no-op, head-of-tm-list precedence parity), 'package epoch' command shape, trace-driven epoch increments on tm/auto_path changes, the epoch-index short-circuit (a .tm added to an already-scanned dir needs 'package epoch incr'), and the pkgIndex.tcl sourcing-scope contract (source_pkgindex, fixed 2026-07-11 - formerly a GAP pin of the global-'dir' clobber): user global 'dir' survives pkg-unknown fallthrough, index scripts see $dir and reach the real ::auto_path (tcllib extension pattern), and their stray unqualified sets no longer leak to ::; plus ::package-override characterisation (`testsuites/discovery/packageoverride.test`, G-176 increment 1, 2026-08-08 - the pre-migration safety net for the commandstack migration: epoch single-arg/indexed query forms and their error arms, the filtered forget arm incl the tcl/Tcl/tcl::oo/tk denial and the no-ifneeded filter, abbreviation acceptance (ep/fo prefixes the parked builtin rejects - a deliberate override delta), delegation pass-through, the re-init no-op guard, and MARKED-TO-FLIP install-shape pins (::package namespace-import origin, ::package:: parked implementation, commandstack::get_stack seeing only the packagepreference record in the two-override interop children covering BOTH real-world install orders - punkshell's pkgpref-first and make.tcl's libunknown-first) - FLIPPED 2026-08-08 with the landed increment-2 migration as marked: they now pin the commandstack-installed shape, real proc at ::package, no ::package:: residue, both overrides as managed stack records (count 2 in both interop orders)). Suite conventions learned the hard way: libunknown 0.3.0 installs its ::package override via commandstack, so cleared-tm-path child probes must pre-source the SOURCE-TREE commandstack by path before libunknown init (children inheriting parent paths resolve it via require); child probes source the SOURCE-TREE libunknown directly by path — 'package require punk::libunknown' tie-breaks the same-version copies (src/modules vs bootsupport) by tm path order, which favours bootsupport in the testinterp and machine env paths in children; and probe scripts must avoid global variable names the handlers use (notably 'dir') — that clobbering misdirected a fixture write into the real Tcl install during test development

25
src/tests/modules/punk/libunknown/testsuites/discovery/discovery.test

@ -145,22 +145,35 @@ namespace eval ::testspace {
set libunknown_ver $thisver
}
}
testConstraint libunknownavailable [expr {$libunknown_src ne ""}]
if {$libunknown_src eq ""} {
puts stderr "discovery.test: cannot locate src/modules/punk/libunknown-*.tm relative to [info script] (tests will be skipped)"
#libunknown 0.3.0 installs its ::package override via commandstack (G-176) -
#init requires it, and the children's tm paths are cleared, so the source-tree
#commandstack is sourced by path first (same highest-version rule)
variable commandstack_src ""
foreach candidate [glob -nocomplain [file join $srcmodules commandstack-*.tm]] {
set thisver [file rootname [lindex [split [file tail $candidate] -] 1]]
if {$commandstack_src eq "" || [package vcompare $thisver $commandstack_ver] == 1} {
set commandstack_src $candidate
set commandstack_ver $thisver
}
}
testConstraint libunknownavailable [expr {$libunknown_src ne "" && $commandstack_src ne ""}]
if {$libunknown_src eq "" || $commandstack_src eq ""} {
puts stderr "discovery.test: cannot locate src/modules/punk/libunknown-*.tm and/or src/modules/commandstack-*.tm relative to [info script] (tests will be skipped)"
}
# -------------------------------------------------------------------------
# Probe: fresh child interp with the source-tree punk::libunknown active and
# tm paths set to exactly $tmdirs. Returns the script's result; child
# deleted after.
# Probe: fresh child interp with the source-tree commandstack + punk::libunknown
# active and tm paths set to exactly $tmdirs. Returns the script's result;
# child deleted after.
# -------------------------------------------------------------------------
proc libu_probe {tmdirs script} {
variable libunknown_src
variable commandstack_src
set i [interp create]
try {
interp eval $i {package prefer latest}
interp eval $i {tcl::tm::remove {*}[tcl::tm::list]}
interp eval $i [list source $commandstack_src]
interp eval $i [list source $libunknown_src]
interp eval $i {punk::libunknown::init}
interp eval $i [list tcl::tm::add {*}$tmdirs]

76
src/tests/modules/punk/libunknown/testsuites/discovery/packageoverride.test

@ -5,12 +5,14 @@
# onto commandstack::rename_command). See goals/G-176-commandstack-doc-integration.md.
#
# Pin classes:
# - BEHAVIOUR pins: must stay green byte-identically through the migration.
# - MARKED-TO-FLIP pins: assert the CURRENT install mechanism (::package is a
# namespace import of ::punk::libunknown::package; the previous
# implementation is parked at ::package::; commandstack sees only the
# packagepreference record when both overrides are live). These flip in the
# G-176 increment-2 commit and in no other.
# - BEHAVIOUR pins: stayed green byte-identically through the migration.
# - MARKED-TO-FLIP pins: originally asserted the pre-migration install
# mechanism (::package as a namespace import of ::punk::libunknown::package;
# previous implementation parked at ::package::; commandstack seeing only
# the packagepreference record when both overrides were live). FLIPPED
# 2026-08-08 with the G-176 increment-2 migration commit as planned: they
# now assert the commandstack-installed shape (real proc at ::package, no
# ::package:: residue, both overrides as managed stack records).
#
# Children are fresh interps sourcing the SOURCE-TREE modules by path (same
# rationale and locator pattern as discovery.test in this directory).
@ -43,23 +45,28 @@ namespace eval ::testspace {
variable commandstack_src [locate_srcmodule {commandstack-*.tm}]
variable pkgpref_src [locate_srcmodule {punk packagepreference-*.tm}]
testConstraint pko_sources [expr {$libunknown_src ne ""}]
#libunknown 0.3.0 init requires commandstack (the override's install mechanism) -
#children have cleared tm paths, so commandstack is sourced by path first
testConstraint pko_sources [expr {$libunknown_src ne "" && $commandstack_src ne ""}]
testConstraint pko_interopsources [expr {$libunknown_src ne "" && $commandstack_src ne "" && $pkgpref_src ne ""}]
if {$libunknown_src eq ""} {
puts stderr "packageoverride.test: cannot locate src/modules/punk/libunknown-*.tm relative to [info script] (tests will be skipped)"
if {$libunknown_src eq "" || $commandstack_src eq ""} {
puts stderr "packageoverride.test: cannot locate src/modules/punk/libunknown-*.tm and/or src/modules/commandstack-*.tm relative to [info script] (tests will be skipped)"
}
# -------------------------------------------------------------------------
# Probe: fresh child interp with the source-tree punk::libunknown sourced and
# init run (override installed). No tm paths are added - the epoch state stays
# at its init values, so epoch pins are deterministic. Child deleted after.
# Probe: fresh child interp with the source-tree commandstack + punk::libunknown
# sourced and init run (override installed). No tm paths are added - the epoch
# state stays at its init values, so epoch pins are deterministic. Child
# deleted after.
# -------------------------------------------------------------------------
proc pko_probe {script} {
variable libunknown_src
variable commandstack_src
set i [interp create]
try {
interp eval $i {package prefer latest}
interp eval $i {tcl::tm::remove {*}[tcl::tm::list]}
interp eval $i [list source $commandstack_src]
interp eval $i [list source $libunknown_src]
interp eval $i {punk::libunknown::init}
interp eval $i $script
@ -91,9 +98,11 @@ namespace eval ::testspace {
interp eval $i [list source $libunknown_src]
interp eval $i {punk::libunknown::init}
} else {
#commandstack sourced first in this order too (libunknown init needs
#it) - the OVERRIDE install order is what the order argument names
interp eval $i [list source $commandstack_src]
interp eval $i [list source $libunknown_src]
interp eval $i {punk::libunknown::init}
interp eval $i [list source $commandstack_src]
interp eval $i [list source $pkgpref_src]
interp eval $i {punk::packagepreference::install}
}
@ -207,26 +216,28 @@ namespace eval ::testspace {
} -result {-1 1 2.5 1}
# -- install shape ------------------------------------------------------------
#MARKED-TO-FLIP (G-176 increment 2): these pin the CURRENT rename+import install
#mechanism - ::package is a namespace import of ::punk::libunknown::package and
#the previous implementation is parked at ::package::. The migration commit
#flips them to the commandstack-installed shape (real proc at ::package, no
#::package:: residue) and nothing else may.
#FLIPPED 2026-08-08 (agent, G-176 increment 2) as marked: previously pinned the
#rename+import mechanism (::package an import of ::punk::libunknown::package,
#previous implementation parked at ::package::). Now pins the commandstack
#install: real proc at ::package, no ::package:: residue, the override present
#as a managed stack record for renamer punk::libunknown.
test override_install_shape {current mechanism: ::package is an import of punk::libunknown::package and ::package:: holds the working previous implementation}\
test override_install_shape {commandstack mechanism: real proc at ::package, no ::package:: residue, the override a managed stack record}\
-constraints pko_sources -body {
pko_probe {
set st [commandstack::get_stack ::package]
list [namespace origin ::package]\
[llength [info commands ::package::]]\
[expr {[::package:: provide Tcl] ne ""}]
[llength $st]\
[dict get [lindex $st 0] renamer]
}
} -result {::punk::libunknown::package 1 1}
} -result {::package 0 1 punk::libunknown}
# -- re-init guard ------------------------------------------------------------
#The no-op contract (second init returns empty and leaves the install alone) is
#BEHAVIOUR and survives the migration. The message text and the import-shaped
#origin element are install-mechanism detail - MARKED-TO-FLIP with increment 2
#if the migrated guard reports differently.
#BEHAVIOUR and survived the migration; the guard is now a commandstack stack
#query for the punk::libunknown record but emits the same 'already done'
#notice. Origin element FLIPPED 2026-08-08 with increment 2 as marked.
test override_reinit_guard {a second init call is a no-op: empty return, an 'already done' stderr notice, install unchanged}\
-constraints pko_sources -body {
@ -247,18 +258,17 @@ namespace eval ::testspace {
[string match "*already done*" $::pko_captured]\
[namespace origin ::package]
}
} -result {1 1 ::punk::libunknown::package}
} -result {1 1 ::package}
# -- packagepreference interop - both real-world install orders ---------------
#punkshell boot: packagepreference (commandstack) first, libunknown on top.
#make.tcl: libunknown alone (packagepreference may arrive later).
#BEHAVIOUR pins: epoch answers and require chains down to the builtin in BOTH
#orders. MARKED-TO-FLIP pins: the commandstack ::package stack sees only the
#packagepreference record (1) while libunknown's rename is unmanaged, and in
#pkgpref_first order ::package is libunknown's import - increment 2 flips the
#stack count to 2 and the origin to the commandstack-installed proc.
#orders. Stack-count and origin elements FLIPPED 2026-08-08 with increment 2
#as marked: both overrides are now managed stack records (count 2) and
#::package is a commandstack-installed proc in both orders.
test override_interop_pkgpref_first {punkshell order: both overrides live - epoch and require both work; stack sees only the managed record (flips to 2)}\
test override_interop_pkgpref_first {punkshell order: both overrides live - epoch and require both work; both records on the managed stack}\
-constraints pko_interopsources -body {
pko_interop pkgpref_first {
set r [list]
@ -270,9 +280,9 @@ namespace eval ::testspace {
lappend r [namespace origin ::package]
set r
}
} -result {1 1.0 1 ::punk::libunknown::package}
} -result {1 1.0 2 ::package}
test override_interop_libunknown_first {make.tcl order then packagepreference on top: epoch reaches through the wrapper's delegation and require works; stack sees only the managed record (flips to 2)}\
test override_interop_libunknown_first {make.tcl order then packagepreference on top: epoch reaches through the wrapper's delegation and require works; both records on the managed stack}\
-constraints pko_interopsources -body {
pko_interop libunknown_first {
set r [list]
@ -284,6 +294,6 @@ namespace eval ::testspace {
lappend r [namespace origin ::package]
set r
}
} -result {1 1.0 1 ::package}
} -result {1 1.0 2 ::package}
}
tcltest::cleanupTests ;#needed to produce test summary line.

Loading…
Cancel
Save