diff --git a/src/bootsupport/modules/oolib-0.1.3.tm b/src/bootsupport/modules/oolib-0.1.4.tm similarity index 98% rename from src/bootsupport/modules/oolib-0.1.3.tm rename to src/bootsupport/modules/oolib-0.1.4.tm index e44e2a8d..0701e6a4 100644 --- a/src/bootsupport/modules/oolib-0.1.3.tm +++ b/src/bootsupport/modules/oolib-0.1.4.tm @@ -136,7 +136,7 @@ namespace eval oolib { error "[self object] collection key must not be an integer. Use another structure if integer keys required" } if {[dict exists $o_data $key]} { - error "[self object] col_processors object error: key '$key' already exists in collection" + error "[self object] collection error: key '$key' already exists in collection" } dict set o_data $key $value return [expr {[dict size $o_data] - 1}] ;#return index of item @@ -196,5 +196,5 @@ namespace eval oolib { package provide oolib [namespace eval oolib { variable version - set version 0.1.3 + set version 0.1.4 }] diff --git a/src/bootsupport/modules/punk/mix/templates-0.2.0.tm b/src/bootsupport/modules/punk/mix/templates-0.2.0.tm index 96b4ab8f..664ca687 100644 Binary files a/src/bootsupport/modules/punk/mix/templates-0.2.0.tm and b/src/bootsupport/modules/punk/mix/templates-0.2.0.tm differ diff --git a/src/bootsupport/modules/punkcheck-0.6.0.tm b/src/bootsupport/modules/punkcheck-0.6.1.tm similarity index 98% rename from src/bootsupport/modules/punkcheck-0.6.0.tm rename to src/bootsupport/modules/punkcheck-0.6.1.tm index e1839e8b..848b6f20 100644 --- a/src/bootsupport/modules/punkcheck-0.6.0.tm +++ b/src/bootsupport/modules/punkcheck-0.6.1.tm @@ -910,14 +910,31 @@ namespace eval punkcheck { #(the record keys are -ts_begin/-ts_end - passing the raw record gave the constructor's # -tsbegin/-tsend defaults, so reconstructed events took 'now' as begin and empty end, # and any later flush/save_installer_record rewrote prior event history with those values) + set eid [punkcheck::dict_getwithdefault $e -id ""] + #self-heal damaged persisted history rather than abort the caller's operation: + #files written by punkcheck < 0.3.2 carry EVENT records whose -id (and + #-source/-targets/-config) hold the LITERAL strings of the old writer's braced + #template ('$eventid' etc - the 0.3.2 buildversion note fixed the writer, but + #files keep the damage) and any two such records collide on the duplicate key + #here. History is advisory - reconstruct the record under a synthesized unique + #id and warn naming the file, instead of failing e.g a whole bake's vfslibs + #phase. Synthesized ids match no INSTALL record's -eventid reference, so + #healed events age out through normal -keep_events pruning. + if {$eid eq "" || $eid in [my events keys]} { + set healed_id "damaged-id-[my events count]-[clock microseconds]" + set shown [string range $eid 0 60] + set why [expr {$eid eq "" ? "missing" : "duplicates an earlier event"}] + puts stderr "punkcheck: WARNING damaged event history in '$o_checkfile': event -id '$shown' $why - keeping record under synthesized id '$healed_id' (safe to ignore; delete the file to reset history)" + set eid $healed_id + } set eargs [list] - lappend eargs -id [punkcheck::dict_getwithdefault $e -id ""] + lappend eargs -id $eid lappend eargs -tsbegin [punkcheck::dict_getwithdefault $e -ts_begin ""] lappend eargs -tsend [punkcheck::dict_getwithdefault $e -ts_end ""] lappend eargs -types [punkcheck::dict_getwithdefault $e -types {}] lappend eargs -config [punkcheck::dict_getwithdefault $e -config {}] - set eobj [punkcheck::installevent create [namespace current]::event_[my events count] [self] [dict get $e -source] [dict get $e -targets] {*}$eargs] - $o_events add $eobj [dict get $e -id] + set eobj [punkcheck::installevent create [namespace current]::event_[my events count] [self] [punkcheck::dict_getwithdefault $e -source ""] [punkcheck::dict_getwithdefault $e -targets ""] {*}$eargs] + $o_events add $eobj $eid } } @@ -1426,7 +1443,7 @@ namespace eval punkcheck { #The -changed flag is computed by comparing -value against the matching virtual SOURCE in the last completed #install record (no filesystem access) - so virtual sources participate in targetset_source_changes like file sources. #Typical use: recording the resolved build version of a module target, so records identify which product of an - #unchanging source fileset (e.g -0.6.0.tm + -buildversion.txt) the target represents. + #unchanging source fileset (e.g -0.6.1.tm + -buildversion.txt) the target represents. proc installsource_add_virtual {file_record id value} { if {![lib::is_file_record_inprogress $file_record]} { error "installsource_add_virtual error: bad file_record - expected FILEINFO with last body element *-INPROGRESS ($file_record)" @@ -2069,11 +2086,11 @@ namespace eval punkcheck { #FILEINFO -targets jjjetc-0.1.0.tm -keep_installrecords 2 -keep_skipped 1 -keep_inprogress 2 { # INSTALL-RECORD -tsiso 2023-09-20T07:30:30 -ts 1695159030266610 -installer punk::mix::cli::build_modules_from_source_to_base -metadata_us 18426 -ts_start_transfer 1695159030285036 -transfer_us 10194 -elapsed_us 28620 { # SOURCE -type file -path ../src/modules/jjjetc-buildversion.txt -cksum c7c71839c36b3d21c8370fed106192fcd659eca9 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 3423 - # SOURCE -type file -path ../src/modules/jjjetc-0.6.0.tm -cksum b646fc2ee88cbd068d2e946fe929b7aea96bd39d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 3413 + # SOURCE -type file -path ../src/modules/jjjetc-0.6.1.tm -cksum b646fc2ee88cbd068d2e946fe929b7aea96bd39d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 1 -metadata_us 3413 # } # INSTALL-SKIPPED -tsiso 2023-09-20T08:14:26 -ts 1695161666087880 -installer punk::mix::cli::build_modules_from_source_to_base -elapsed_us 18914 { # SOURCE -type file -path ../src/modules/jjjetc-buildversion.txt -cksum c7c71839c36b3d21c8370fed106192fcd659eca9 -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 0 -metadata_us 3435 - # SOURCE -type file -path ../src/modules/jjjetc-0.6.0.tm -cksum b646fc2ee88cbd068d2e946fe929b7aea96bd39d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 0 -metadata_us 3338 + # SOURCE -type file -path ../src/modules/jjjetc-0.6.1.tm -cksum b646fc2ee88cbd068d2e946fe929b7aea96bd39d -cksum_all_opts {-cksum_content 1 -cksum_meta 0 -cksum_acls 0 -cksum_usetar 0 -cksum_algorithm sha1} -changed 0 -metadata_us 3338 # } #} @@ -2778,6 +2795,6 @@ namespace eval ::punk::args::register { package provide punkcheck [namespace eval punkcheck { set pkg punkcheck variable version - set version 0.6.0 + set version 0.6.1 }] return diff --git a/src/modules/oolib-999999.0a1.0.tm b/src/modules/oolib-999999.0a1.0.tm index fb68d7cb..7664c691 100644 --- a/src/modules/oolib-999999.0a1.0.tm +++ b/src/modules/oolib-999999.0a1.0.tm @@ -136,7 +136,7 @@ namespace eval oolib { error "[self object] collection key must not be an integer. Use another structure if integer keys required" } if {[dict exists $o_data $key]} { - error "[self object] col_processors object error: key '$key' already exists in collection" + error "[self object] collection error: key '$key' already exists in collection" } dict set o_data $key $value return [expr {[dict size $o_data] - 1}] ;#return index of item diff --git a/src/modules/oolib-buildversion.txt b/src/modules/oolib-buildversion.txt index 1f3befba..d6c98a5b 100644 --- a/src/modules/oolib-buildversion.txt +++ b/src/modules/oolib-buildversion.txt @@ -1,3 +1,4 @@ -0.1.3 +0.1.4 #First line must be a tm version number -#all other lines are ignored. \ No newline at end of file +#all other lines are ignored. +#0.1.4 - collection add duplicate-key error message no longer misnames the object as 'col_processors' (stray copy-paste in the generic collection class) \ No newline at end of file diff --git a/src/modules/punkcheck-999999.0a1.0.tm b/src/modules/punkcheck-999999.0a1.0.tm index 5684014b..8237752a 100644 --- a/src/modules/punkcheck-999999.0a1.0.tm +++ b/src/modules/punkcheck-999999.0a1.0.tm @@ -910,14 +910,31 @@ namespace eval punkcheck { #(the record keys are -ts_begin/-ts_end - passing the raw record gave the constructor's # -tsbegin/-tsend defaults, so reconstructed events took 'now' as begin and empty end, # and any later flush/save_installer_record rewrote prior event history with those values) + set eid [punkcheck::dict_getwithdefault $e -id ""] + #self-heal damaged persisted history rather than abort the caller's operation: + #files written by punkcheck < 0.3.2 carry EVENT records whose -id (and + #-source/-targets/-config) hold the LITERAL strings of the old writer's braced + #template ('$eventid' etc - the 0.3.2 buildversion note fixed the writer, but + #files keep the damage) and any two such records collide on the duplicate key + #here. History is advisory - reconstruct the record under a synthesized unique + #id and warn naming the file, instead of failing e.g a whole bake's vfslibs + #phase. Synthesized ids match no INSTALL record's -eventid reference, so + #healed events age out through normal -keep_events pruning. + if {$eid eq "" || $eid in [my events keys]} { + set healed_id "damaged-id-[my events count]-[clock microseconds]" + set shown [string range $eid 0 60] + set why [expr {$eid eq "" ? "missing" : "duplicates an earlier event"}] + puts stderr "punkcheck: WARNING damaged event history in '$o_checkfile': event -id '$shown' $why - keeping record under synthesized id '$healed_id' (safe to ignore; delete the file to reset history)" + set eid $healed_id + } set eargs [list] - lappend eargs -id [punkcheck::dict_getwithdefault $e -id ""] + lappend eargs -id $eid lappend eargs -tsbegin [punkcheck::dict_getwithdefault $e -ts_begin ""] lappend eargs -tsend [punkcheck::dict_getwithdefault $e -ts_end ""] lappend eargs -types [punkcheck::dict_getwithdefault $e -types {}] lappend eargs -config [punkcheck::dict_getwithdefault $e -config {}] - set eobj [punkcheck::installevent create [namespace current]::event_[my events count] [self] [dict get $e -source] [dict get $e -targets] {*}$eargs] - $o_events add $eobj [dict get $e -id] + set eobj [punkcheck::installevent create [namespace current]::event_[my events count] [self] [punkcheck::dict_getwithdefault $e -source ""] [punkcheck::dict_getwithdefault $e -targets ""] {*}$eargs] + $o_events add $eobj $eid } } diff --git a/src/modules/punkcheck-buildversion.txt b/src/modules/punkcheck-buildversion.txt index 422d65c6..3af9c815 100644 --- a/src/modules/punkcheck-buildversion.txt +++ b/src/modules/punkcheck-buildversion.txt @@ -1,6 +1,7 @@ -0.6.0 +0.6.1 #First line must be a semantic version number #all other lines are ignored. +#0.6.1 - installtrack constructor self-heals damaged persisted event history instead of aborting the caller: EVENT records with a missing or duplicate -id (notably the literal '$eventid' records written by punkcheck < 0.3.2 - the 0.3.2 note fixed the writer but existing files keep the damage, and the modern keyed event reload collided on them, killing e.g a whole bake's vfslibs phase; first field hit 2026-07-27 on a machine whose src/vfs/.punkcheck predated 0.3.2) are reconstructed under synthesized unique ids ('damaged-id-...') with a stderr warning naming the file. Synthesized ids match no INSTALL record's -eventid reference, so healed events age out through normal -keep_events pruning. Event reload also tolerates missing -source/-targets keys. Regression test: src/tests/modules/punkcheck/testsuites/punkcheck/installtrack.test (installtrack_damaged_event_history_selfheal). #0.6.0 - G-095 concurrent-writer safety: saves are write-temp-then-atomic-rename (bounded retry for Windows sharing violations) so readers never see torn content, and load_records_from_file retries transient open denials during a writer's replace window (parse errors stay strict). New punkcheck::lock namespace: advisory sibling lockfile .lock (open WRONLY CREAT EXCL, holder pid/host/installer/timestamp contents, channel held open, in-process refcounted re-acquisition) serializes whole installer events - acquired at start_event, released at event end / installtrack destroy; flushes and chokepoint saves outside an event take a transient lock; contention retries with backoff to -timeout (env PUNKCHECK_LOCK_TIMEOUT) then errors naming the holder (errorcode PUNKCHECK LOCK TIMEOUT); stale locks break on provably-dead holder pid (twapi/kill -0, capability-gated) or age (-staleage / PUNKCHECK_LOCK_STALEAGE). Deferred flushes merge own records (INSTALLER by -name, touched FILEINFO by -targets) into freshly-loaded file state instead of wholesale overwrite; an mtime/size tripwire warns when a non-protocol writer touched the file between load and save. Duplicate-INSTALLER recovery degrades to a clean actionable error when stdin is non-interactive (new punkcheck::lib::stdin_is_interactive; twapi GetConsoleMode on the real stdin handle / unix -inputmode / test -t 0), and the interactive prompt gains the synced-targets deletion-wedge caveat. default_excludefiletail_core adds .punkcheck.* so lock/temp siblings are never treated as installable content. #0.5.0 - G-094 single record lifecycle: installtrack/installevent OO layer is the sole implementation (targetset init/add-source/started/end) with a per-event persistence policy (start_event ?-persistence eager|deferred?; deferred = in-memory working recordset + $installer flush, the mode punkcheck::install now uses as a tree-walking OO consumer). Legacy proc pipeline (start_installer_event, installfile_begin/started_install/finished_install/skipped_install) retired to error-with-pointer shims. All saves flow through one chokepoint (installtrack save_working_recordset - G-095 attachment point). Fixes: start_event refreshes the working recordset (sequential events no longer clobber prior targetset writes; end/end_event now clears the active event so sequential events work), installtrack as_record no longer writes -name with a trailing space, event reconstruction maps -ts_begin/-ts_end (flush no longer rewrites prior event history timestamps), get_recordlist misspelled variable, targetset_dict called a nonexistent helper. punkcheck::install records now carry -metadata_us/-ts_start_transfer/-transfer_us/-cksum_us like OO-installed records, and all-targets mode now stores -targets_cksums. recordlist::extract_or_create_fileset_record no longer prints to stdout (isnew returned; targetset_init reports via debugchannel). Dead targetset class removed. #0.4.0 - cross-root sources: installfile_add_source_and_fetch_metadata now records sources that share no common root with the punkcheck folder (e.g //zipfs:/ module-mounted sources installing to the filesystem) as independent absolute paths via the cksum helpers' empty-base mode, instead of erroring 'don't share a common root'. Enables punkcheck::install from module-carried (vfs-mounted) layout payloads - G-087 stage 3 bare-kit project generation. diff --git a/src/tests/modules/punkcheck/testsuites/punkcheck/installtrack.test b/src/tests/modules/punkcheck/testsuites/punkcheck/installtrack.test index fc786fe8..5f2adbb1 100644 --- a/src/tests/modules/punkcheck/testsuites/punkcheck/installtrack.test +++ b/src/tests/modules/punkcheck/testsuites/punkcheck/installtrack.test @@ -613,5 +613,55 @@ namespace eval ::testspace { file delete -force [dict get $ws root] }\ -result 0 + + # --- installtrack: reload self-heals damaged persisted event history --- + # Files written by punkcheck < 0.3.2 carry EVENT records whose -id is the + # LITERAL string {$eventid} (the 0.3.2 buildversion note fixed the writer; + # existing files keep the damage). Two such records collide when the modern + # constructor reloads events keyed by -id - which killed whole make.tcl + # bakes (vfslibs phase) on checkouts with old .punkcheck files. The + # constructor must reconstruct such records under synthesized unique ids + # (with a stderr warning) rather than error. Added 2026-07-27 (agent, + # punkcheck 0.6.1). + test installtrack_damaged_event_history_selfheal {reload of a file with duplicate literal \$eventid EVENT records self-heals instead of erroring}\ + -setup { + set ws [make_workspace] + set punkcheck_file [dict get $ws punkcheck_file] + #seed two completed events so the persisted INSTALLER body carries two EVENT records + foreach step {one two} { + set installer [punkcheck::installtrack new "test" $punkcheck_file] + $installer set_source_target [dict get $ws srcdir] [dict get $ws tgtdir] + set event [$installer start_event [list -install_step $step]] + $event end + $installer destroy + } + #simulate the pre-0.3.2 damage: every persisted event -id becomes the + #literal string $eventid - BRACED, as the damaged files carry it (the + #record loader evaluates records, so only a braced literal survives + #as the string; the -eventid keys of INSTALL records are a different + #token and are not touched by the \s-id\s pattern) + set fd [open $punkcheck_file r] + set data [read $fd] + close $fd + regsub -all {(\s-id\s+)\S+} $data {\1{$eventid}} data + set fd [open $punkcheck_file w] + puts -nonewline $fd $data + close $fd + }\ + -body { + set result [list] + set installer [punkcheck::installtrack new "test" $punkcheck_file] + lappend result reloaded_events [$installer events count] + set keys [$installer events keys] + lappend result distinct_keys [expr {[llength $keys] == [llength [lsort -unique $keys]]}] + lappend result healed_count [llength [lsearch -all -glob $keys damaged-id-*]] + lappend result literal_kept [expr {{$eventid} in $keys}] + $installer destroy + set result + }\ + -cleanup { + file delete -force [dict get $ws root] + }\ + -result {reloaded_events 2 distinct_keys 1 healed_count 1 literal_kept 1} } tcltest::cleanupTests ;#needed to produce test summary line. \ No newline at end of file