From a70ddfbdb3ee11fc99143bc46945c77a5cc3c79f Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Fri, 31 Jul 2026 20:10:23 +1000 Subject: [PATCH] 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//), same-name two-non-native coexistence without runtime-rename, same-target duplicate keeps _; 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 --- goals/G-127-crosstarget-vfs-bake.md | 26 ++- src/make.tcl | 14 +- .../punkexe/maketclkitlocations.test | 183 ++++++++++++++++++ 3 files changed, 213 insertions(+), 10 deletions(-) create mode 100644 src/tests/shell/testsuites/punkexe/maketclkitlocations.test diff --git a/goals/G-127-crosstarget-vfs-bake.md b/goals/G-127-crosstarget-vfs-bake.md index 97fbfe4a..76cb2fbf 100644 --- a/goals/G-127-crosstarget-vfs-bake.md +++ b/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// 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 + _ 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. diff --git a/src/make.tcl b/src/make.tcl index eb38c51c..caeea7ba 100644 --- a/src/make.tcl +++ b/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 @ (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 } diff --git a/src/tests/shell/testsuites/punkexe/maketclkitlocations.test b/src/tests/shell/testsuites/punkexe/maketclkitlocations.test new file mode 100644 index 00000000..4afc4bb4 --- /dev/null +++ b/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/ + bin/ every launcher expects, the non-default-target kit +# under the kits// 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 _ +# 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 /bin/punk902z.exe +#then /bin/punkshell902. Skipped (constraint punkexeavailable) if none found. + +namespace eval ::testspace { + namespace import ::tcltest::* + + variable testdir [file dirname [file normalize [info script]]] + #/src/tests/shell/testsuites/punkexe -> 5 levels up to + 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 script src/make.tcl 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 , output + #. + 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// 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// 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 _ 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