From 19c63ac60eeae71cdc5ab506e92264a0a95a12d8 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Sat, 25 Jul 2026 04:51:56 +1000 Subject: [PATCH] punk-runtime list -remote identity columns (G-119 follow-on, user-directed) The starred active row previously never changed across 'use' switches (every -rN materializes to the same working name) and carried the name-keyed dead end '(not listed on server)' - true of the name, unexplanatory for a working copy whose bytes match a server artifact. Rows the server listing does not name now carry an untitled identity middle column: from= for materialized working copies (the beside-toml source; content verified against that artifact's server sha1 so the Remote cell shows a real status - Same version, CONTENT DIFFERS for the immutable-artifact anomaly, or '(artifact not listed on server)'), sha1= for tomlless byte-copies (default-first- then-ordinal pick, the same rule as the freshness fallback so row identity and freshness verdict always name the same artifact). '(not listed on server)' remains only for genuinely unknown files; the (server default) row annotation follows artifact identity onto working copies of the default. Table header/rules widened to the three-column layout, payload row formats now column-aligned across ps1/bash; help text updated; bin/punk-runtime.cmd rewrapped (checkfile clean). Chosen shape (user-approved from mockup variants): untitled middle column with self-keyed from=/sha1= tags - consistent with the plain list metadata summary's from= vocabulary and self-describing when a row is pasted without its header. runtimecmd_freshness.test +3 row-shape tests (identity columns, the identity-following default annotation, anomaly statuses) with ps1/bash parity: 10/10 pass; scriptwrap + binscripts battery 35 pass / 1 pre-existing skip / 0 fail (roundtrip + checkfile pins green, WSL row pins unaffected). bin/AGENTS.md + shell tests AGENTS updated. punkshell 0.22.0. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- CHANGELOG.md | 12 ++ bin/AGENTS.md | 15 +- bin/punk-runtime.cmd | 171 +++++++++++++++--- punkproject.toml | 2 +- src/scriptapps/bin/punk-runtime.bash | 85 +++++++-- src/scriptapps/bin/punk-runtime.ps1 | 86 ++++++++- src/tests/shell/AGENTS.md | 2 +- .../binscripts/runtimecmd_freshness.test | 79 ++++++++ 8 files changed, 403 insertions(+), 49 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f353bd8b..ff6f2b85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ 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.22.0] - 2026-07-25 + +- punk-runtime `list -remote` identity columns (G-119 follow-on; both payloads + rewrapped + `bin/punk-runtime.cmd`): rows the server listing does not name carry an untitled middle + column - `from=` for materialized working copies (content verified against the + source artifact's server sha1, so the Remote cell shows a real status: Same version / + CONTENT DIFFERS / artifact-not-listed) and `sha1=` for byte-copies of server + artifacts. The starred active row now changes with every `use` switch instead of sitting + on the dead-end "(not listed on server)" (which now means genuinely unknown), and the + `(server default)` row annotation follows artifact identity. Table header/rules widened + to the three-column layout. + ## [0.21.0] - 2026-07-25 - punk-runtime freshness verdict (G-119; both payloads + rewrapped `bin/punk-runtime.cmd`): diff --git a/bin/AGENTS.md b/bin/AGENTS.md index d7e8d875..9e88d5ac 100644 --- a/bin/AGENTS.md +++ b/bin/AGENTS.md @@ -154,7 +154,20 @@ first ordinal/C-collation match wins in both payloads). A NO-NAME `fetch` (the active diverges from the resolved default - silent when current, and BEHIND carries an inline `use ` hint since the artifact was just fetched. Verdict lines are byte-identical across the payloads; only the guidance lines carry each payload's -program-name convention. Pinned by +program-name convention. Identity middle column (2026-07-25 follow-on, user-decided +shape): `list -remote` rows the server listing does not name carry an UNTITLED +middle column - `from=` for a materialized working copy (its beside-toml +source artifact; content is then verified against that artifact's server sha1, so +the Remote cell shows a real status: `Same version`, `CONTENT DIFFERS` for the +immutable-artifact anomaly, or `(artifact not listed on server)`), and +`sha1=` for a tomlless byte-copy of a server artifact (default-first-then- +ordinal pick - the same rule as the freshness fallback, so row identity and verdict +always name the same artifact). `(not listed on server)` remains only for genuinely +unidentified files, and the `(server default)` row annotation follows artifact +identity (a working copy of the default carries it). Rationale: the starred active +row is the salient element - it must change with every `use` switch and teach the +working-copy mechanics, and column position carries meaning (identity phrasing +inside the Remote column would read as a server fact). Pinned by `src/tests/shell/testsuites/binscripts/runtimecmd_freshness.test` against a fixture punkbin server (`PUNKBIN_URL` override + `src/tests/testsupport/httpfixture.tcl`). diff --git a/bin/punk-runtime.cmd b/bin/punk-runtime.cmd index 3d4fbc1b..ec773d36 100755 --- a/bin/punk-runtime.cmd +++ b/bin/punk-runtime.cmd @@ -1543,6 +1543,11 @@ show_help() { echo " visible at a glance. When both are known, a freshness verdict" echo " states current/ahead/behind/incomparable explicitly (revision" echo " comparison, sha1 identity fallback; update guidance on behind)." + echo " Rows the server listing does not name carry an identity column:" + echo " from= for a materialized working copy (content verified" + echo " against that artifact), sha1= for a byte-copy of a server" + echo " artifact - only genuinely unknown files stay '(not listed on" + echo " server)'." echo " use ?-platform

?" echo " Select the runtime that 'run' launches (records active.toml in" echo " the platform folder). An immutable -r ARTIFACT name is" @@ -1874,7 +1879,7 @@ case "$action" in if [[ -f "$defaultslocal" ]]; then platform_default=$(awk -v p="$archtail" '$1==p {print $2; exit}' "$defaultslocal") fi - echo "-----------------------------------------------------------------------" + echo "----------------------------------------------------------------------------------------" echo "Runtimes for $archtail" echo "Local $archdir" echo "Remote ${url_kitbase}/${archtail}" @@ -1901,24 +1906,78 @@ case "$action" in #annotation above already says so) freshness_verdict "$activename" "$platform_default" "$sha1local" list fi - echo "-----------------------------------------------------------------------" - echo " Local Remote" - echo "-----------------------------------------------------------------------" + echo "----------------------------------------------------------------------------------------" + printf ' %-35s %-33s %s\n' "Local" "" "Remote" + echo "----------------------------------------------------------------------------------------" for f in $(list_candidates); do local_sha1=$(lcase "$(sha1_of "$archdir/$f")") stored_sha1=$(grep -E "^[0-9a-fA-F]{40} \*${f}\$" "$sha1local" | head -n 1 | cut -d' ' -f1) - if [[ -z "$stored_sha1" ]]; then - rhs="(not listed on server)" - elif [[ "$local_sha1" == "$(lcase "$stored_sha1")" ]]; then - rhs="Same version" + mid="" + ident="" + if [[ -n "$stored_sha1" ]]; then + if [[ "$local_sha1" == "$(lcase "$stored_sha1")" ]]; then + rhs="Same version" + else + rhs="UPDATE AVAILABLE" + fi else - rhs="UPDATE AVAILABLE" + #G-119 refinement: identity middle column for rows the server + #listing does not name. A materialized working copy is identified + #by its beside-toml source artifact (from=) with its CONTENT + #verified against that artifact's server sha1; a tomlless stray by + #sha1 VALUE-match (sha1=). The Remote column then carries a real + #server relationship for such rows instead of the dead-end + #'(not listed on server)', which now means genuinely unknown. + rowtoml="$archdir/$(rootname_of "$f").toml" + artname="" + if [[ -f "$rowtoml" ]]; then + artname=$(sed -n 's/^[[:space:]]*name[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$rowtoml" | head -n 1) + [[ "$artname" == "$f" ]] && artname="" + fi + if [[ -n "$artname" ]]; then + mid="from=$artname" + ident="$artname" + artsha=$(lcase "$(tr -d '\r' < "$sha1local" | grep -E "^[0-9a-fA-F]{40} \*${artname}\$" | head -n 1 | cut -d' ' -f1)") + if [[ -z "$artsha" ]]; then + rhs="(artifact not listed on server)" + elif [[ "$local_sha1" == "$artsha" ]]; then + rhs="Same version" + else + #-r artifacts are immutable - differing content means a + #modified local copy or a mismatched sidecar, not an update + rhs="CONTENT DIFFERS" + fi + else + #sha1 value-match: the default's row first, else the first + #C-collation match with support files excluded - the same pick + #rule as freshness_verdict, so the row identity and the + #freshness line always name the same artifact + vname="" + if [[ -n "$platform_default" ]]; then + dsha=$(lcase "$(tr -d '\r' < "$sha1local" | grep -E "^[0-9a-fA-F]{40} \*${platform_default}\$" | head -n 1 | cut -d' ' -f1)") + [[ -n "$dsha" && "$local_sha1" == "$dsha" ]] && vname="$platform_default" + fi + if [[ -z "$vname" ]]; then + vname=$(tr -d '\r' < "$sha1local" | awk -v h="$local_sha1" '{n=$2; sub(/^\*/,"",n); if (tolower($1)==h) print n}' | grep -Ev '\.(txt|toml|tm|tmp|log)$' | LC_ALL=C sort | head -n 1) + fi + if [[ -n "$vname" ]]; then + mid="sha1=$vname" + ident="$vname" + rhs="Same version" + else + rhs="(not listed on server)" + fi + fi fi mark=" " [[ "$f" == "$activename" ]] && mark="* " annot="" - [[ -n "$platform_default" && "$f" == "$platform_default" ]] && annot=" (server default)" - printf '%s%-35s %s%s\n' "$mark" "$f" "$rhs" "$annot" + #the (server default) annotation follows artifact identity: a working + #copy of (or byte-copy of) the default artifact is the default + if [[ -n "$platform_default" ]] && [[ "$f" == "$platform_default" || "$ident" == "$platform_default" ]]; then + annot=" (server default)" + fi + printf '%s%-35s %-33s %s%s\n' "$mark" "$f" "$mid" "$rhs" "$annot" done #remote-only entries, sorted (ps1-payload parity - its dict enumeration #is explicitly sorted). tr strips CRLF \r: msys grep strips it in text @@ -1934,10 +1993,10 @@ case "$action" in if [[ -n "$rname" && ! -f "$archdir/$rname" ]]; then annot="" [[ -n "$platform_default" && "$rname" == "$platform_default" ]] && annot=" (server default)" - printf ' %-35s %s%s\n' "-" "$rname" "$annot" + printf ' %-35s %-33s %s%s\n' "-" "" "$rname" "$annot" fi done - echo "-----------------------------------------------------------------------" + echo "----------------------------------------------------------------------------------------" echo "* = active (local selection)" else if [[ -d "$archdir" ]]; then @@ -2667,6 +2726,11 @@ function Show-PunkRuntimeHelp { write-host " visible at a glance. When both are known, a freshness verdict" write-host " states current/ahead/behind/incomparable explicitly (revision" write-host " comparison, sha1 identity fallback; update guidance on behind)." + write-host " Rows the server listing does not name carry an identity column:" + write-host " from= for a materialized working copy (content verified" + write-host " against that artifact), sha1= for a byte-copy of a server" + write-host " artifact - only genuinely unknown files stay '(not listed on" + write-host " server)'." write-host " use ?-platform

?" write-host " Select the runtime that 'run' launches (records active.toml in" write-host " the platform folder). An immutable -r ARTIFACT name is" @@ -3603,7 +3667,7 @@ function psmain { } } } - Write-host "-----------------------------------------------------------------------" + Write-host "----------------------------------------------------------------------------------------" Write-host "Runtimes for $arch" Write-host "Local $archfolder" Write-host "Remote $archurl" @@ -3633,9 +3697,9 @@ function psmain { #annotation above already says so) Write-PunkFreshnessVerdict -archfolder $archfolder -activename $activename -defaultname $platform_default -sha1local $sha1local -arch $arch -mode 'list' } - Write-host "-----------------------------------------------------------------------" - Write-host " Local Remote" - Write-host "-----------------------------------------------------------------------" + Write-host "----------------------------------------------------------------------------------------" + Write-host (" {0,-35} {1,-33} {2}" -f "Local", "", "Remote") + Write-host "----------------------------------------------------------------------------------------" # 12345678910234567892023456789302345 $G = "`e[32m" #Green $Y = "`e[33m" #Yellow @@ -3649,6 +3713,8 @@ function psmain { [array]::Sort($localkeys, [System.StringComparer]::Ordinal) foreach ($key in $localkeys) { $local_sha1 = $($localdict[$key]) + $mid = "" + $ident = "" if ($remotedict.ContainsKey($key)) { if ($local_sha1 -eq $remotedict[$key]) { $rhs = "Same version" @@ -3659,7 +3725,63 @@ function psmain { } } else { $C = $R - $rhs = "(not listed on server)" + #G-119 refinement: identity middle column for rows the server + #listing does not name. A materialized working copy is + #identified by its beside-toml source artifact (from=) with + #its CONTENT verified against that artifact's server sha1; a + #tomlless stray by sha1 VALUE-match (sha1=). The Remote + #column then carries a real server relationship for such + #rows instead of the dead-end '(not listed on server)', + #which now means genuinely unknown. + $rowtoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $key) + ".toml") + $artname = "" + if (Test-Path -Path $rowtoml -PathType Leaf) { + foreach ($tline in (Get-Content -Path $rowtoml)) { + $m = [regex]::Match($tline, '^\s*name\s*=\s*"(.*)"\s*$') + if ($m.Success) { $artname = $m.Groups[1].Value; break } + } + if ($artname -eq $key) { $artname = "" } + } + if ($artname -ne "") { + $mid = "from=$artname" + $ident = $artname + if (-not $remotedict.ContainsKey($artname)) { + $rhs = "(artifact not listed on server)" + } elseif ($local_sha1 -eq $remotedict[$artname]) { + $rhs = "Same version" + } else { + #-r artifacts are immutable - differing content + #means a modified local copy or a mismatched + #sidecar, not an update + $rhs = "CONTENT DIFFERS" + } + } else { + #sha1 value-match: the default's row first, else the + #first ordinal match with support files excluded - the + #same pick rule as Write-PunkFreshnessVerdict, so the + #row identity and the freshness line always name the + #same artifact + $vname = "" + if ($platform_default -ne "" -and $remotedict.ContainsKey($platform_default) -and $remotedict[$platform_default] -eq $local_sha1) { + $vname = $platform_default + } + if ($vname -eq "") { + $rnames = @($remotedict.Keys) + [array]::Sort($rnames, [System.StringComparer]::Ordinal) + foreach ($rn in $rnames) { + $rext = [System.IO.Path]::GetExtension($rn) + if ($(".txt",".toml",".tm",".tmp",".log") -contains $rext) { continue } + if ($remotedict[$rn] -eq $local_sha1) { $vname = $rn; break } + } + } + if ($vname -ne "") { + $mid = "sha1=$vname" + $ident = $vname + $rhs = "Same version" + } else { + $rhs = "(not listed on server)" + } + } } #ansi problems from cmd.exe not in windows terminal - review $C = "" @@ -3667,12 +3789,15 @@ function psmain { $mark = " " if ($key -eq $activename) { $mark = "* " } $annot = "" - if ($platform_default -ne "" -and $key -eq $platform_default) { $annot = " (server default)" } + #the (server default) annotation follows artifact identity: a + #working copy of (or byte-copy of) the default artifact is the + #default + if ($platform_default -ne "" -and ($key -eq $platform_default -or $ident -eq $platform_default)) { $annot = " (server default)" } $lhs = "$key".PadRight(35, ' ') - write-host -nonewline "${mark}${C}${lhs}${RST}" + $midp = "$mid".PadRight(33, ' ') + write-host -nonewline "${mark}${C}${lhs}${RST} ${midp} " write-host "$rhs$annot" } - $lhs_missing = "-".PadRight(35, ' ') $remotekeys = @($remotedict.Keys) [array]::Sort($remotekeys, [System.StringComparer]::Ordinal) foreach ($key in $remotekeys) { @@ -3684,11 +3809,11 @@ function psmain { if ($(".txt",".toml",".tm",".tmp",".log") -contains $rext) { continue } $annot = "" if ($platform_default -ne "" -and $key -eq $platform_default) { $annot = " (server default)" } - write-host -nonewline " $lhs_missing" + write-host -nonewline (" {0,-35} {1,-33} " -f "-", "") write-host "$key$annot" } } - Write-host "-----------------------------------------------------------------------" + Write-host "----------------------------------------------------------------------------------------" Write-host "* = active (local selection)" } else { diff --git a/punkproject.toml b/punkproject.toml index 7c904a1d..faa4c7d2 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,4 +1,4 @@ [project] name = "punkshell" -version = "0.21.0" +version = "0.22.0" license = "BSD-2-Clause" diff --git a/src/scriptapps/bin/punk-runtime.bash b/src/scriptapps/bin/punk-runtime.bash index a181f63a..57c2f65c 100644 --- a/src/scriptapps/bin/punk-runtime.bash +++ b/src/scriptapps/bin/punk-runtime.bash @@ -263,6 +263,11 @@ show_help() { echo " visible at a glance. When both are known, a freshness verdict" echo " states current/ahead/behind/incomparable explicitly (revision" echo " comparison, sha1 identity fallback; update guidance on behind)." + echo " Rows the server listing does not name carry an identity column:" + echo " from= for a materialized working copy (content verified" + echo " against that artifact), sha1= for a byte-copy of a server" + echo " artifact - only genuinely unknown files stay '(not listed on" + echo " server)'." echo " use ?-platform

?" echo " Select the runtime that 'run' launches (records active.toml in" echo " the platform folder). An immutable -r ARTIFACT name is" @@ -594,7 +599,7 @@ case "$action" in if [[ -f "$defaultslocal" ]]; then platform_default=$(awk -v p="$archtail" '$1==p {print $2; exit}' "$defaultslocal") fi - echo "-----------------------------------------------------------------------" + echo "----------------------------------------------------------------------------------------" echo "Runtimes for $archtail" echo "Local $archdir" echo "Remote ${url_kitbase}/${archtail}" @@ -621,24 +626,78 @@ case "$action" in #annotation above already says so) freshness_verdict "$activename" "$platform_default" "$sha1local" list fi - echo "-----------------------------------------------------------------------" - echo " Local Remote" - echo "-----------------------------------------------------------------------" + echo "----------------------------------------------------------------------------------------" + printf ' %-35s %-33s %s\n' "Local" "" "Remote" + echo "----------------------------------------------------------------------------------------" for f in $(list_candidates); do local_sha1=$(lcase "$(sha1_of "$archdir/$f")") stored_sha1=$(grep -E "^[0-9a-fA-F]{40} \*${f}\$" "$sha1local" | head -n 1 | cut -d' ' -f1) - if [[ -z "$stored_sha1" ]]; then - rhs="(not listed on server)" - elif [[ "$local_sha1" == "$(lcase "$stored_sha1")" ]]; then - rhs="Same version" + mid="" + ident="" + if [[ -n "$stored_sha1" ]]; then + if [[ "$local_sha1" == "$(lcase "$stored_sha1")" ]]; then + rhs="Same version" + else + rhs="UPDATE AVAILABLE" + fi else - rhs="UPDATE AVAILABLE" + #G-119 refinement: identity middle column for rows the server + #listing does not name. A materialized working copy is identified + #by its beside-toml source artifact (from=) with its CONTENT + #verified against that artifact's server sha1; a tomlless stray by + #sha1 VALUE-match (sha1=). The Remote column then carries a real + #server relationship for such rows instead of the dead-end + #'(not listed on server)', which now means genuinely unknown. + rowtoml="$archdir/$(rootname_of "$f").toml" + artname="" + if [[ -f "$rowtoml" ]]; then + artname=$(sed -n 's/^[[:space:]]*name[[:space:]]*=[[:space:]]*"\(.*\)".*/\1/p' "$rowtoml" | head -n 1) + [[ "$artname" == "$f" ]] && artname="" + fi + if [[ -n "$artname" ]]; then + mid="from=$artname" + ident="$artname" + artsha=$(lcase "$(tr -d '\r' < "$sha1local" | grep -E "^[0-9a-fA-F]{40} \*${artname}\$" | head -n 1 | cut -d' ' -f1)") + if [[ -z "$artsha" ]]; then + rhs="(artifact not listed on server)" + elif [[ "$local_sha1" == "$artsha" ]]; then + rhs="Same version" + else + #-r artifacts are immutable - differing content means a + #modified local copy or a mismatched sidecar, not an update + rhs="CONTENT DIFFERS" + fi + else + #sha1 value-match: the default's row first, else the first + #C-collation match with support files excluded - the same pick + #rule as freshness_verdict, so the row identity and the + #freshness line always name the same artifact + vname="" + if [[ -n "$platform_default" ]]; then + dsha=$(lcase "$(tr -d '\r' < "$sha1local" | grep -E "^[0-9a-fA-F]{40} \*${platform_default}\$" | head -n 1 | cut -d' ' -f1)") + [[ -n "$dsha" && "$local_sha1" == "$dsha" ]] && vname="$platform_default" + fi + if [[ -z "$vname" ]]; then + vname=$(tr -d '\r' < "$sha1local" | awk -v h="$local_sha1" '{n=$2; sub(/^\*/,"",n); if (tolower($1)==h) print n}' | grep -Ev '\.(txt|toml|tm|tmp|log)$' | LC_ALL=C sort | head -n 1) + fi + if [[ -n "$vname" ]]; then + mid="sha1=$vname" + ident="$vname" + rhs="Same version" + else + rhs="(not listed on server)" + fi + fi fi mark=" " [[ "$f" == "$activename" ]] && mark="* " annot="" - [[ -n "$platform_default" && "$f" == "$platform_default" ]] && annot=" (server default)" - printf '%s%-35s %s%s\n' "$mark" "$f" "$rhs" "$annot" + #the (server default) annotation follows artifact identity: a working + #copy of (or byte-copy of) the default artifact is the default + if [[ -n "$platform_default" ]] && [[ "$f" == "$platform_default" || "$ident" == "$platform_default" ]]; then + annot=" (server default)" + fi + printf '%s%-35s %-33s %s%s\n' "$mark" "$f" "$mid" "$rhs" "$annot" done #remote-only entries, sorted (ps1-payload parity - its dict enumeration #is explicitly sorted). tr strips CRLF \r: msys grep strips it in text @@ -654,10 +713,10 @@ case "$action" in if [[ -n "$rname" && ! -f "$archdir/$rname" ]]; then annot="" [[ -n "$platform_default" && "$rname" == "$platform_default" ]] && annot=" (server default)" - printf ' %-35s %s%s\n' "-" "$rname" "$annot" + printf ' %-35s %-33s %s%s\n' "-" "" "$rname" "$annot" fi done - echo "-----------------------------------------------------------------------" + echo "----------------------------------------------------------------------------------------" echo "* = active (local selection)" else if [[ -d "$archdir" ]]; then diff --git a/src/scriptapps/bin/punk-runtime.ps1 b/src/scriptapps/bin/punk-runtime.ps1 index d6b50689..0c66b033 100644 --- a/src/scriptapps/bin/punk-runtime.ps1 +++ b/src/scriptapps/bin/punk-runtime.ps1 @@ -124,6 +124,11 @@ function Show-PunkRuntimeHelp { write-host " visible at a glance. When both are known, a freshness verdict" write-host " states current/ahead/behind/incomparable explicitly (revision" write-host " comparison, sha1 identity fallback; update guidance on behind)." + write-host " Rows the server listing does not name carry an identity column:" + write-host " from= for a materialized working copy (content verified" + write-host " against that artifact), sha1= for a byte-copy of a server" + write-host " artifact - only genuinely unknown files stay '(not listed on" + write-host " server)'." write-host " use ?-platform

?" write-host " Select the runtime that 'run' launches (records active.toml in" write-host " the platform folder). An immutable -r ARTIFACT name is" @@ -1060,7 +1065,7 @@ function psmain { } } } - Write-host "-----------------------------------------------------------------------" + Write-host "----------------------------------------------------------------------------------------" Write-host "Runtimes for $arch" Write-host "Local $archfolder" Write-host "Remote $archurl" @@ -1090,9 +1095,9 @@ function psmain { #annotation above already says so) Write-PunkFreshnessVerdict -archfolder $archfolder -activename $activename -defaultname $platform_default -sha1local $sha1local -arch $arch -mode 'list' } - Write-host "-----------------------------------------------------------------------" - Write-host " Local Remote" - Write-host "-----------------------------------------------------------------------" + Write-host "----------------------------------------------------------------------------------------" + Write-host (" {0,-35} {1,-33} {2}" -f "Local", "", "Remote") + Write-host "----------------------------------------------------------------------------------------" # 12345678910234567892023456789302345 $G = "`e[32m" #Green $Y = "`e[33m" #Yellow @@ -1106,6 +1111,8 @@ function psmain { [array]::Sort($localkeys, [System.StringComparer]::Ordinal) foreach ($key in $localkeys) { $local_sha1 = $($localdict[$key]) + $mid = "" + $ident = "" if ($remotedict.ContainsKey($key)) { if ($local_sha1 -eq $remotedict[$key]) { $rhs = "Same version" @@ -1116,7 +1123,63 @@ function psmain { } } else { $C = $R - $rhs = "(not listed on server)" + #G-119 refinement: identity middle column for rows the server + #listing does not name. A materialized working copy is + #identified by its beside-toml source artifact (from=) with + #its CONTENT verified against that artifact's server sha1; a + #tomlless stray by sha1 VALUE-match (sha1=). The Remote + #column then carries a real server relationship for such + #rows instead of the dead-end '(not listed on server)', + #which now means genuinely unknown. + $rowtoml = Join-Path -Path $archfolder -ChildPath ((Get-PunkRuntimeRootName $key) + ".toml") + $artname = "" + if (Test-Path -Path $rowtoml -PathType Leaf) { + foreach ($tline in (Get-Content -Path $rowtoml)) { + $m = [regex]::Match($tline, '^\s*name\s*=\s*"(.*)"\s*$') + if ($m.Success) { $artname = $m.Groups[1].Value; break } + } + if ($artname -eq $key) { $artname = "" } + } + if ($artname -ne "") { + $mid = "from=$artname" + $ident = $artname + if (-not $remotedict.ContainsKey($artname)) { + $rhs = "(artifact not listed on server)" + } elseif ($local_sha1 -eq $remotedict[$artname]) { + $rhs = "Same version" + } else { + #-r artifacts are immutable - differing content + #means a modified local copy or a mismatched + #sidecar, not an update + $rhs = "CONTENT DIFFERS" + } + } else { + #sha1 value-match: the default's row first, else the + #first ordinal match with support files excluded - the + #same pick rule as Write-PunkFreshnessVerdict, so the + #row identity and the freshness line always name the + #same artifact + $vname = "" + if ($platform_default -ne "" -and $remotedict.ContainsKey($platform_default) -and $remotedict[$platform_default] -eq $local_sha1) { + $vname = $platform_default + } + if ($vname -eq "") { + $rnames = @($remotedict.Keys) + [array]::Sort($rnames, [System.StringComparer]::Ordinal) + foreach ($rn in $rnames) { + $rext = [System.IO.Path]::GetExtension($rn) + if ($(".txt",".toml",".tm",".tmp",".log") -contains $rext) { continue } + if ($remotedict[$rn] -eq $local_sha1) { $vname = $rn; break } + } + } + if ($vname -ne "") { + $mid = "sha1=$vname" + $ident = $vname + $rhs = "Same version" + } else { + $rhs = "(not listed on server)" + } + } } #ansi problems from cmd.exe not in windows terminal - review $C = "" @@ -1124,12 +1187,15 @@ function psmain { $mark = " " if ($key -eq $activename) { $mark = "* " } $annot = "" - if ($platform_default -ne "" -and $key -eq $platform_default) { $annot = " (server default)" } + #the (server default) annotation follows artifact identity: a + #working copy of (or byte-copy of) the default artifact is the + #default + if ($platform_default -ne "" -and ($key -eq $platform_default -or $ident -eq $platform_default)) { $annot = " (server default)" } $lhs = "$key".PadRight(35, ' ') - write-host -nonewline "${mark}${C}${lhs}${RST}" + $midp = "$mid".PadRight(33, ' ') + write-host -nonewline "${mark}${C}${lhs}${RST} ${midp} " write-host "$rhs$annot" } - $lhs_missing = "-".PadRight(35, ' ') $remotekeys = @($remotedict.Keys) [array]::Sort($remotekeys, [System.StringComparer]::Ordinal) foreach ($key in $remotekeys) { @@ -1141,11 +1207,11 @@ function psmain { if ($(".txt",".toml",".tm",".tmp",".log") -contains $rext) { continue } $annot = "" if ($platform_default -ne "" -and $key -eq $platform_default) { $annot = " (server default)" } - write-host -nonewline " $lhs_missing" + write-host -nonewline (" {0,-35} {1,-33} " -f "-", "") write-host "$key$annot" } } - Write-host "-----------------------------------------------------------------------" + Write-host "----------------------------------------------------------------------------------------" Write-host "* = active (local selection)" } else { diff --git a/src/tests/shell/AGENTS.md b/src/tests/shell/AGENTS.md index 44f3858c..f4a162f0 100644 --- a/src/tests/shell/AGENTS.md +++ b/src/tests/shell/AGENTS.md @@ -22,7 +22,7 @@ Tests for shell-level behavior, command-line execution, and stdin/stdout interac - punkexe tests run against the built binary: after changing punk::repl or app-punkshell source, rebuild via `make.tcl packages`, `make.tcl vfscommonupdate -confirm 0` (unattended runs must pass the flag - the REPLACE confirmation aborts fast on non-interactive stdin instead of reading a piped `y`), then `make.tcl project` before trusting results; `make.tcl project` alone does not refresh `_vfscommon.vfs`. - `testsuites/punkexe/tclshcmd.test` covers the `tclsh` subcommand (punk_main.tcl dispatch): script-file sourcing (argv0/argv/info-script state, honest exit codes incl. explicit exit), piped no-arg stdin evaluation, stock argument forms (leading `-` arg = no script, all args stay in `::argv`; `-encoding name file` sources with the named encoding, incomplete `-encoding` forms fall through to argv; verified byte-level via a utf-8/iso8859-1 fixture), the `lib:` refusal (plain-tclsh semantics - pointer to the `script` subcommand, exit 1), and - on kits built on a TCLSH_PIPEREPL-patched runtime (G-096/G-103, see `src/buildsuites/suite_tcl90/patches/README.md`) - the piperepl launch-state contract: ::tclsh(istty) published, piped script-arg launch state all-zero, unconsumed piped input NOT evaluated unless the script sets `::tclsh(evalinput) 1`, and the script's right to consume stdin itself; plus the degraded-mode notice on unpatched runtimes. Patched-kit resolution: `env(PUNK_PIPEREPL_TEST_EXE)` if it probes as patched, else the punkexe if patched, else `bin/punk9_beta.exe`/`bin/punk9bi_beta.exe`. HANG RULE: no PIPED test or fixture may set `::tclsh(dorepl)` - that path reopens stdin from the console (CONIN$) and blocks under the piped punk_run harness. Console-reopen coverage exists but is env-gated (G-118 item 11; constraint `punkconsole` = windows + env `PUNK_TEST_CONSOLE=1`, normal runs skip): two tests drive the dorepl path end-to-end in the child's OWN hidden console per the G-106 recipe - an outer `Start-Process -WindowStyle Hidden` powershell wrapper owns a fresh console and runs ` tclsh` there with std handles redirected to files, then `testsupport/consoleinject.ps1` attaches to that console and types the verification lines + `exit 0` as key events. They pin: `::tclsh(reopened)` flips 0 -> 1 at reopen while `istty` stays 0 (immutable launch fact), `::tclsh(inputbuffer)` appears empty (the repl loop consumed all piped input), the reopen notice is silent by default and appears on STDERR only under `TCLSH_PIPEREPL_DEBUG=1`, and the injected exit completes the process (exit code 0). Deadlines plus wrapper-tree kill keep them hang-proof. - `testsuites/binscripts/runtimebash_wsl.test` (G-059) exercises the punk-runtime.cmd unix payload (`src/scriptapps/bin/punk-runtime.bash`) through WSL: active/use/run resolution, env override, stale-active guidance, single-candidate fallback, and `list -remote` local-vs-server comparison (offline via a crafted cached sha1sums.txt + `PUNKBIN_URL` pointed at an unreachable address). Gated by the `wsllinux` capability constraint from `src/tests/testsupport/wslprobe.tcl`; all execution happens in a WSL-native staging dir, and a final test asserts the Windows checkout's git status is unchanged by the run. -- `testsuites/binscripts/runtimecmd_freshness.test` (G-119) exercises the punk-runtime freshness verdict (active-vs-server-default: current/ahead/behind/incomparable) in BOTH payloads against a fixture punkbin server: a child-process http file server (`testsupport/httpfixture.tcl`) serves a crafted layout (defaults.txt, sha1sums, artifacts + metadata tomls) via the `PUNKBIN_URL` override, with all execution in a tempdir staging area on a fixture platform folder (`testplat-x86_64` - the real `bin/runtime` store is untouched). The powershell payload runs as the staged committed `bin/punk-runtime.cmd` via cmd.exe (the wrap-pinned path); the bash payload is driven DIRECTLY under a probed msys/git bash (uname MINGW*/MSYS*; WSL bash is rejected - under WSL2 it cannot reach the 127.0.0.1 fixture server, and on windows the polyglot routes unix shells to the powershell payload anyway). Asserts the G-119 acceptance scenarios (ahead/behind+guidance/current-annotation/pre-family no-basis and hash-identity/no-name-fetch note) with ps1-vs-bash verdict-line parity. Gated by capability constraints (windows, committed cmd, bash source, certutil, msys bash, fixture server started); skips cleanly elsewhere. +- `testsuites/binscripts/runtimecmd_freshness.test` (G-119) exercises the punk-runtime freshness verdict (active-vs-server-default: current/ahead/behind/incomparable) in BOTH payloads against a fixture punkbin server: a child-process http file server (`testsupport/httpfixture.tcl`) serves a crafted layout (defaults.txt, sha1sums, artifacts + metadata tomls) via the `PUNKBIN_URL` override, with all execution in a tempdir staging area on a fixture platform folder (`testplat-x86_64` - the real `bin/runtime` store is untouched). The powershell payload runs as the staged committed `bin/punk-runtime.cmd` via cmd.exe (the wrap-pinned path); the bash payload is driven DIRECTLY under a probed msys/git bash (uname MINGW*/MSYS*; WSL bash is rejected - under WSL2 it cannot reach the 127.0.0.1 fixture server, and on windows the polyglot routes unix shells to the powershell payload anyway). Asserts the G-119 acceptance scenarios (ahead/behind+guidance/current-annotation/pre-family no-basis and hash-identity/no-name-fetch note) with ps1-vs-bash verdict-line parity, plus the identity middle column row shapes (2026-07-25 follow-on): `from=` working-copy rows with content-verified Remote statuses (Same version / CONTENT DIFFERS / artifact-not-listed), `sha1=` byte-copy strays, and the identity-following `(server default)` row annotation. Gated by capability constraints (windows, committed cmd, bash source, certutil, msys bash, fixture server started); skips cleanly elsewhere. - `testsuites/punkexe/staticruntime.test` (G-058) targets a kit built on a runtime with statically-linked packages: resolved from `env(PUNK_STATICKIT_TEST_EXE)`, else `/bin/punk91.exe`, gated by the capability-probed `statickitavailable` constraint (a probe run must show a captured `::punkboot::static_packages` baseline including Thread - not mere file existence). It asserts repl boot without the codethread Thread failure, Thread resolution in the code interp / fabricated interps / snapshot-seeded threads, and twapi resolution per the static-vs-bundled policy. The verification runtime (tclsfe-x64.exe) is pinned in the punkbin artifact repo so the constraint is satisfiable off the original dev machine. Kit script-exec paths must be passed with forward slashes. - GOAL tests (constraint `punkgoals`, enabled by env `PUNK_TEST_GOALS=1`) cover the piped-stdin-then-interactive-shell drop-in (`shellpipe_eof_then_interactive_shell`, `shellpipe_script_and_eof_then_interactive_shell`); they are excluded from normal runs because they require an openable console in the test environment and briefly attach a live shell to it - they pass in console-attached environments against a current build. - The eof-policy discriminator is `env(PUNK_PIPE_EOF)` (`exit`|`interactive`|unset=console heuristic) implemented in app-punkshell; termination-guard tests spawn children with `exit`, goal tests spawn with it unset. diff --git a/src/tests/shell/testsuites/binscripts/runtimecmd_freshness.test b/src/tests/shell/testsuites/binscripts/runtimecmd_freshness.test index cdc9e358..1ceea9d0 100644 --- a/src/tests/shell/testsuites/binscripts/runtimecmd_freshness.test +++ b/src/tests/shell/testsuites/binscripts/runtimecmd_freshness.test @@ -459,6 +459,85 @@ namespace eval ::testspace { }\ -result [list 0 1 0 1] + #added 2026-07-25 (agent, G-119) - follow-on refinement (user-directed shape): + #identity middle column (from=/sha1=) so the salient starred row changes with + #every 'use' switch and the Remote column carries a real server relationship + + test runtimecmd_freshness_identity_columns {rows the server listing does not name carry identity: from= on a materialized working copy (starred) and sha1= on a byte-copy stray, both with a real Remote status - the dead-end '(not listed on server)' no longer appears for identified rows - both payloads}\ + -constraints $C\ + -setup { + set result "" + variable artifacts + reset_stage + stage_materialized fakert-alpha.exe fakert-alpha-r2.exe 2 + stage_file oldkit.exe [dict get $artifacts fakert-alpha-r2.exe] + set_default fakert-alpha-r1.exe + }\ + -body { + foreach r [list [run_cmdroute list -remote -platform testplat-x86_64] \ + [run_bashroute list -remote -platform testplat-x86_64]] { + set out [dict get $r output] + lappend result [dict get $r exit] + lappend result [regexp {\* fakert-alpha\.exe\s+from=fakert-alpha-r2\.exe\s+Same version} $out] + lappend result [regexp {oldkit\.exe\s+sha1=fakert-alpha-r2\.exe\s+Same version} $out] + lappend result [string match "*(not listed on server)*" $out] + } + set result + }\ + -cleanup { + }\ + -result [list 0 1 1 0 0 1 1 0] + + test runtimecmd_freshness_identity_default_annotation {the (server default) row annotation follows artifact identity: a working copy materialized from the default artifact carries it on the starred row - both payloads}\ + -constraints $C\ + -setup { + set result "" + reset_stage + stage_materialized fakert-alpha.exe fakert-alpha-r1.exe 1 + set_default fakert-alpha-r1.exe + }\ + -body { + foreach r [list [run_cmdroute list -remote -platform testplat-x86_64] \ + [run_bashroute list -remote -platform testplat-x86_64]] { + lappend result [dict get $r exit] + lappend result [regexp {\* fakert-alpha\.exe\s+from=fakert-alpha-r1\.exe\s+Same version\s+\(server default\)} [dict get $r output]] + } + set result + }\ + -cleanup { + }\ + -result [list 0 1 0 1] + + test runtimecmd_freshness_identity_anomaly_statuses {identified working copies surface anomalies: content differing from the immutable source artifact reports CONTENT DIFFERS, and a source artifact absent from the server listing is stated - both payloads}\ + -constraints $C\ + -setup { + set result "" + variable artifacts + reset_stage + #working copy claiming r1 provenance but carrying r2 bytes (modified local + #copy / mismatched sidecar - artifacts are immutable, so never an "update") + stage_file fakert-alpha.exe [dict get $artifacts fakert-alpha-r2.exe] + stage_file fakert-alpha.toml [artifact_toml fakert-alpha-r1.exe 1] + #working copy whose recorded source artifact is not on the server + stage_file fakert-beta.exe [dict get $artifacts fakert-beta-r1.exe] + stage_file fakert-beta.toml [artifact_toml fakert-beta-r9.exe 9] + set_active fakert-alpha.exe + set_default fakert-alpha-r1.exe + }\ + -body { + foreach r [list [run_cmdroute list -remote -platform testplat-x86_64] \ + [run_bashroute list -remote -platform testplat-x86_64]] { + set out [dict get $r output] + lappend result [dict get $r exit] + lappend result [regexp {fakert-alpha\.exe\s+from=fakert-alpha-r1\.exe\s+CONTENT DIFFERS} $out] + lappend result [regexp {fakert-beta\.exe\s+from=fakert-beta-r9\.exe\s+\(artifact not listed on server\)} $out] + } + set result + }\ + -cleanup { + }\ + -result [list 0 1 1 0 1 1] + #whole-file teardown: stop the fixture server, restore shielded env state if {$fixtureok} { server_stop