Browse Source

G-127 increment 3: kit-location characterization + multi-record name selection fix

- new punkexe/maketclkitlocations.test (PUNK_MAPVFS_CONFIG fixtures, nothing
  built): two-target one-vfs distinct locations (flat vs kits/<platform>/),
  same-name two-non-native coexistence without runtime-rename, same-target
  duplicate keeps <name>_<runtime>; mismatch clause referenced as G-133
  regression pin (binaryarch.test + maketclpayloadcheck.test)
- mapvfs_match_outputs: a plain kit name now selects ALL matching records (one
  per target) instead of the first hit - pre-G-127 uniqueness assumption removed;
  filtered bakelist + selective bake cover every target of a shared name
- full punkexe subtree pass: 15 files, 101 tests, 0 failed (4 constraint skips)

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 1 week ago
parent
commit
a70ddfbdb3
  1. 26
      goals/G-127-crosstarget-vfs-bake.md
  2. 14
      src/make.tcl
  3. 183
      src/tests/shell/testsuites/punkexe/maketclkitlocations.test

26
goals/G-127-crosstarget-vfs-bake.md

@ -246,12 +246,26 @@ exists locally (empty but for sha1sums.txt) and punkbin upstream carries
pre-axis bake), deploys to bin/kits/linux-x86_64/; bake punkdeclare
(platform-neutral entries) unchanged, smoke probe resolves udp 1.0.13 +
tcllibc 2.0 from the freshly built native artifact.
- Remaining for acceptance: two-target one-vfs characterization via the
PUNK_MAPVFS_CONFIG seam (one-run distinct artifacts, cross-run no-overwrite,
two-non-native coexistence, per-target payload selection, mismatch-report
regression pin per the G-133 redundancy note); byte-comparability spot-check
for a zip-type native kit; existing punkexe suites pass as a set;
bin/AGENTS.md + src/AGENTS.md output-location docs; G-023 reconciliation
- 2026-07-31 increment 3 (characterization): new
src/tests/shell/testsuites/punkexe/maketclkitlocations.test - fixture kit
mappings through the PUNK_MAPVFS_CONFIG seam (nothing built, per the G-125
precedent of keeping heavy verdicts out of this suite): one vfs under two
targets resolves to flat vs kits/<platform>/ locations in one report
(location = pure function of (name, target) - the construction that makes the
cross-run overwrite impossible); two NON-NATIVE targets sharing a kit name
coexist as two rows split by tier and target-driven suffix with NO
runtime-name rename; a same-name SAME-target duplicate keeps the historical
<name>_<runtime> disambiguation. The mismatch clause stays a REGRESSION PIN on
G-133 coverage (verdicts: modules/punkboot/utils/testsuites/utils/binaryarch.test;
advisory contract: maketclpayloadcheck.test) - deliberately not re-implemented.
SURFACE FIX flushed out by the characterization: mapvfs_match_outputs stopped
at the FIRST record matching a plain kit name (a pre-G-127 uniqueness
assumption) - a name now selects ALL its records (one per target), matching
the @group branch, so filtered bakelist detail and selective bake cover every
target of a shared name. Full punkexe subtree green: 15 files, 101 tests,
97 pass / 4 constraint-skips / 0 fail (88s, native Tcl903 runner).
- Remaining for acceptance: byte-comparability spot-check for a zip-type native
kit; bin/AGENTS.md + src/AGENTS.md output-location docs; G-023 reconciliation
statement recorded here; punkshell902 + win32-ix86 relocation records
finalized; punk9linux.vfs remaining drop-ins (tcltls/tdom/thread .so)
recorded as deliberately hand-curated or migrated.

14
src/make.tcl

@ -1307,7 +1307,9 @@ proc ::punkboot::lib::mapvfs_kit_outputs {model rtbase sourcefolder} {
# Match user-supplied kit names against mapvfs_kit_outputs records.
# Accepts the base kit name (punk91), the platform artifact name (punk91.exe,
# name.kit), or a G-024 group selector @<groupname> (selects every record in the
# group); matching is case-insensitive when the HOST filesystem is (a name the
# group). A base name matching several records - one per target, coexisting under
# their own output tiers (G-127) - selects them all.
# Matching is case-insensitive when the HOST filesystem is (a name the
# user types is a host-side convenience - a cygwin-family host is as
# case-insensitive as a native windows one, whatever it targets). Returns a dict:
# selected matched records (kit-output order, deduplicated)
@ -1345,18 +1347,22 @@ proc ::punkboot::lib::mapvfs_match_outputs {kit_outputs names} {
}
set i 0
foreach rec $kit_outputs {
set rec_match 0
foreach candidate [list [dict get $rec kitname] [dict get $rec targetkit]] {
if {$candidate eq ""} {continue}
if {[apply $name_eq $on_windows $candidate $base] || [apply $name_eq $on_windows $candidate $name]} {
set found 1
set rec_match 1
break
}
}
if {$found} {
if {$rec_match} {
#G-127: one kit NAME may legitimately match several records - one per
#target, coexisting under their own output tiers - so a name selects
#them ALL (like the @group branch), not just the first hit
set found 1
if {$i ni $selected_idx} {
lappend selected_idx $i
}
break
}
incr i
}

183
src/tests/shell/testsuites/punkexe/maketclkitlocations.test

@ -0,0 +1,183 @@
package require tcltest
#Piped characterization of the G-127 target-keyed kit OUTPUT LOCATION model, through
#the make.tcl bakelist report against fixture kit mappings (PUNK_MAPVFS_CONFIG env
#seam - the real mapping is never edited, nothing is built). Runs the WORKING TREE's
#src/make.tcl under the built punk executable's 'script' subcommand and pins:
# - one vfs definition paired with two different targets resolves to DISTINCT
# output locations in one report: the default-target kit at the flat
# src/_build/<kit> + bin/<kit> every launcher expects, the non-default-target kit
# under the kits/<platform>/ tier in both, with the row carrying target= and
# out= notes (location is a pure function of (name, target), which is what makes
# the pre-G-127 cross-run overwrite impossible by construction)
# - two NON-NATIVE targets sharing one kit name coexist as two rows whose locations
# differ by platform tier (and by target-driven .exe suffixing) with NO
# runtime-name disambiguation rename
# - a same-NAME same-TARGET duplicate still gets the historical <name>_<runtime>
# disambiguation - the guard's remaining job after G-127 scoped it per target
#The payload/target MISMATCH clause of G-127's Acceptance is a regression pin on
#G-133 behaviour characterized elsewhere: the scan verdict in
#modules/punkboot/utils/testsuites/utils/binaryarch.test, the advisory contract in
#maketclpayloadcheck.test. Real cross-target ARTIFACT evidence (bake punkshell902 ->
#bin/kits/linux-x86_64/punkshell902, per-platform %platform% payload selection) is
#recorded in goals/G-127-crosstarget-vfs-bake.md Progress - a full bake is too heavy
#for this suite by design (see the G-125 precedent note in maketclbootgate.test).
#
#Target executable resolved from env(PUNK_SHELL_TEST_EXE), else <projectroot>/bin/punk902z.exe
#then <projectroot>/bin/punkshell902. Skipped (constraint punkexeavailable) if none found.
namespace eval ::testspace {
namespace import ::tcltest::*
variable testdir [file dirname [file normalize [info script]]]
#<projectroot>/src/tests/shell/testsuites/punkexe -> 5 levels up to <projectroot>
variable projectroot [file normalize [file join $testdir .. .. .. .. ..]]
variable maketcl [file join $projectroot src make.tcl]
variable punkexe ""
if {[info exists ::env(PUNK_SHELL_TEST_EXE)] && $::env(PUNK_SHELL_TEST_EXE) ne ""} {
set punkexe [file normalize $::env(PUNK_SHELL_TEST_EXE)]
} else {
foreach candidate [list [file join $projectroot bin punk902z.exe] [file join $projectroot bin punkshell902]] {
if {[file exists $candidate]} {
set punkexe $candidate
break
}
}
}
testConstraint punkexeavailable [expr {$punkexe ne "" && [file exists $punkexe]}]
variable maketcl_run_timeout_ms 60000
variable runstate
array set runstate {}
proc maketcl_run_read {chan} {
variable runstate
append runstate(output) [read $chan]
if {[chan eof $chan]} {
chan event $chan readable {}
set runstate(done) eof
}
}
#Run <punkexe> script src/make.tcl <subcommand...> with output captured through a
#pipe (stdin half-closed for immediate EOF - make.tcl must never wait on stdin for
#these subcommands). Returns dict: timedout 0|1, exitcode <int|"">, output
#<combined stdout+stderr>.
proc maketcl_run {cmdargs} {
variable runstate
variable maketcl_run_timeout_ms
variable punkexe
variable maketcl
array unset runstate
set runstate(output) ""
set runstate(done) ""
set chan [open |[list $punkexe script $maketcl {*}$cmdargs 2>@1] r+]
chan configure $chan -blocking 0 -translation binary
catch {chan close $chan write} ;#no stdin for the child - immediate EOF
set timerid [after $maketcl_run_timeout_ms [list set [namespace current]::runstate(done) timeout]]
chan event $chan readable [list [namespace current]::maketcl_run_read $chan]
while {$runstate(done) eq ""} {
vwait [namespace current]::runstate(done)
}
after cancel $timerid
set timedout [expr {$runstate(done) eq "timeout"}]
set exitcode ""
if {$timedout} {
catch {exec {*}[auto_execok taskkill] /F /T /PID [lindex [pid $chan] 0]}
catch {chan close $chan}
} else {
chan configure $chan -blocking 1
if {[catch {chan close $chan} errdata errdict]} {
set exitcode [lindex [dict get $errdict -errorcode] end]
} else {
set exitcode 0
}
}
return [dict create timedout $timedout exitcode $exitcode output $runstate(output)]
}
#Fixture kit mappings through the PUNK_MAPVFS_CONFIG env seam (fixture files in
#the tcltest temp dir - the real mapping is never edited). Same helper shape as
#maketclbakelist.test.
proc maketcl_run_mapseam {fixturename fixturelines cmdargs} {
set fixture [tcltest::makeFile [join $fixturelines \n] $fixturename]
set ::env(PUNK_MAPVFS_CONFIG) [file normalize $fixture]
try {
return [maketcl_run $cmdargs]
} finally {
unset -nocomplain ::env(PUNK_MAPVFS_CONFIG)
tcltest::removeFile $fixturename
}
}
#added 2026-07-31 (agent, G-127)
test maketcl_bakelist_twotarget_onevfs_distinct_locations {one vfs definition under two targets: default-target kit keeps flat locations (no target=/out= notes), non-default-target kit resolves under kits/<platform>/ in both src/_build and bin} -constraints {punkexeavailable} -body {
set r [maketcl_run_mapseam fixture_twotarget.toml {
{[kit.fixnat]}
{runtime = "tclsfe-x64"}
{vfs = "punk9wintk903.vfs"}
{type = "zip"}
{}
{[kit.fixlin]}
{runtime = "tclkit-902-Linux64-intel-dyn"}
{vfs = "punk9wintk903.vfs"}
{type = "kit"}
{target = "linux-x86_64"}
} {bakelist fixnat fixlin}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
#default-target row: flat locations, no target=/out= notes (runtime=missing may
#legitimately appear on a host without the runtime - only the location notes matter)
lappend result natrow [regexp {(?n)^fixnat\s+zip\s+tclsfe-x64\s+punk9wintk903\.vfs\s} $out]
lappend result natnonotes [expr {![regexp {(?n)^fixnat\s.*(target=|out=)} $out]}]
lappend result natbuild [regexp {(?n)^\s+build product: src/_build/fixnat\.exe } $out]
lappend result natdeploy [regexp {(?n)^\s+deployed:\s+bin/fixnat\.exe } $out]
#non-default-target row: kits/<platform>/ tier both sides, suffixless for linux
lappend result linrow [regexp {(?n)^fixlin\s+kit\s+tclkit-902-Linux64-intel-dyn\s+punk9wintk903\.vfs\s+\S+\s.*target=linux-x86_64 out=kits/linux-x86_64/} $out]
lappend result linbuild [regexp {(?n)^\s+build product: src/_build/kits/linux-x86_64/fixlin } $out]
lappend result lindeploy [regexp {(?n)^\s+deployed:\s+bin/kits/linux-x86_64/fixlin } $out]
set result
} -result {timedout 0 exitcode 0 natrow 1 natnonotes 1 natbuild 1 natdeploy 1 linrow 1 linbuild 1 lindeploy 1}
#added 2026-07-31 (agent, G-127)
test maketcl_bakelist_same_name_two_nonnative_coexist {two NON-NATIVE targets sharing one kit name: two rows, locations split by platform tier (and target-driven .exe suffix), no runtime-name rename} -constraints {punkexeavailable} -body {
#legacy line format carries an explicit kitname element, letting two entries
#declare the SAME output name for different targets
set r [maketcl_run_mapseam fixture_samename.config {
{tclkit-902-Linux64-intel-dyn {punk9wintk903.vfs fixsame kit linux-x86_64}}
{tclsh8.6.10-luck-zip.exe {punk9wintk903.vfs fixsame zip win32-ix86}}
} {bakelist fixsame}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result linrow [regexp {(?n)^fixsame\s+kit\s+tclkit-902-Linux64-intel-dyn\s+punk9wintk903\.vfs\s+\S+\s.*out=kits/linux-x86_64/} $out]
lappend result ixrow [regexp {(?n)^fixsame\s+zip\s+tclsh8\.6\.10-luck-zip\s+punk9wintk903\.vfs\s+\S+\s.*out=kits/win32-ix86/} $out]
#the pre-G-127 behaviour disambiguated the second row by RUNTIME name - pin its absence
lappend result norename [expr {![regexp {(?n)^fixsame_} $out]}]
#distinct artifacts by construction: same name, two tiers, suffix follows target
lappend result linbuild [regexp {(?n)^\s+build product: src/_build/kits/linux-x86_64/fixsame } $out]
lappend result ixbuild [regexp {(?n)^\s+build product: src/_build/kits/win32-ix86/fixsame\.exe } $out]
set result
} -result {timedout 0 exitcode 0 linrow 1 ixrow 1 norename 1 linbuild 1 ixbuild 1}
#added 2026-07-31 (agent, G-127)
test maketcl_bakelist_same_name_same_target_still_renamed {a same-name SAME-target duplicate keeps the historical <name>_<runtime> disambiguation} -constraints {punkexeavailable} -body {
set r [maketcl_run_mapseam fixture_sametarget.config {
{tclsfe-x64.exe {punk9wintk903.vfs fixdup zip}}
{tclsh905.exe {punk9wintk905.vfs fixdup zip}}
} {bakelist}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result firstrow [regexp {(?n)^fixdup\s+zip\s+tclsfe-x64\s} $out]
lappend result renamedrow [regexp {(?n)^fixdup_tclsh905\s+zip\s+tclsh905\s} $out]
set result
} -result {timedout 0 exitcode 0 firstrow 1 renamedrow 1}
cleanupTests
}
namespace delete ::testspace
Loading…
Cancel
Save