You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

11 KiB

G-094 punkcheck single record lifecycle: OO installtrack as sole implementation with a persistence policy

Status: achieved 2026-07-21 Scope: src/modules/punkcheck-999999.0a1.0.tm (installtrack/installevent lifecycle unification, installfile_* retirement, punkcheck::install as OO consumer), src/modules/punk/mix/cli-999999.0a1.0.tm + src/make.tcl (consumers verified unchanged), src/tests/modules/punkcheck/testsuites/punkcheck/ (characterization + new equivalence/staleness coverage) Goal: The installtrack/installevent OO layer is the only implementation of the .punkcheck record lifecycle (targetset init / add-source / started / end), with an explicit per-event persistence policy - eager per-operation read-modify-write (current OO behaviour) and deferred event-scoped flush (current batch behaviour, QUERY's no-save being the in-object precedent) - punkcheck::install rebuilt as a tree-walking consumer of that layer in deferred mode, the zero-caller legacy pipeline (installfile_begin / installfile_finished_install / installfile_skipped_install) retired to error-with-pointer shims, and every .punkcheck save flowing through a single chokepoint. Acceptance: characterization suites install.test and installtrack.test pass (pins flipped only where this goal's contract deliberately changes behaviour, each flip recorded); a new test pins field equivalence between batch-installed and OO-installed records (including -targets_cksums, which the legacy proc pipeline never stored); a new regression test covers o_record_list coherence - two sequential events on one installtrack instance, with targetset writes in the first event, lose nothing at the second start_event; grep of src/modules, src/make.tcl and src/project_layouts finds no live caller of the retired trio outside shims and tests; a full make.tcl build completes with skip/copy decisions unchanged against pre-refactor .punkcheck state (no spurious recopies of unchanged sources).

Context

2026-07-19 module review found three parallel implementations of the record lifecycle, with drift:

  • Legacy proc pipeline (installfile_begin/started/finished/skipped + start_installer_event): frozen at an earlier design - INSTALL-only (no MODIFY/DELETE/VIRTUAL/QUERY), no FAILED status, no -note, and installfile_finished_install stores no post-install target cksums, so its records can never serve -overwrite synced-targets. Zero live callers of begin/finished/skipped anywhere (punk::mix::cli's calls are commented out next to their OO replacements); they are kept alive only by installfile_help and the characterization tests.
  • OO layer (installtrack/installevent): targetset_started, targetset_addsource and targetset_addsource_virtual delegate down to the legacy procs, while targetset_init and targetset_end are parallel rewrites of installfile_begin and finished/skipped with the extensions above - a half-inversion of the desired layering (procs should be the facade, the OO lifecycle the substrate).
  • Batch punkcheck::install: bypasses both - uses start_installer_event and the metadata fetcher but inlines its own begin and finalize so it can defer all persistence to one end-of-run save, independently re-growing -targets_cksums. Its in-memory recordset handling is the deferred persistence mode this goal makes first-class.

Latent single-process clobber: start_event saves the whole constructor-era o_record_list, which targetset operations never refresh (they re-load from file into locals) - a second start_event on one instance would discard every FILEINFO update the first event's targetsets wrote. Unexercised-path evidence: installtrack get_recordlist reads a misspelled variable ($o_recordlist) and targetset_dict calls a nonexistent punk::records_as_target_dict - both would error if ever called.

Performance shape that motivates the policy knob rather than "just save eagerly everywhere": eager OO costs three full parse+prettyprint rewrites of a MB-scale file per installed target (modules/.punkcheck is ~1.9MB), which is why the batch path defers; deferring must be a first-class mode of the one lifecycle, not an ad-hoc bypass with its own record code.

Approach

  • Persistence policy selected per event (eager | deferred); in deferred mode the installtrack's in-memory recordset is authoritative between flushes, so the o_record_list coherence fix is a precondition, not a side quest.
  • installfile_add_source_and_fetch_metadata stays as the shared pure engine - it is the one correctly-layered piece (record transform + filesystem metadata fetch, no saves), used by all three current paths.
  • start_installer_event's role is subsumed by installtrack construction + start_event; the surviving convenience surface for callers is punkcheck::install / install_tm_files / install_non_tm_files as thin OO consumers.
  • Single save chokepoint: all writes to a .punkcheck file (event start, eager targetset ops, deferred flush) route through one proc/method - this is the attachment point G-095 locks onto.

Alternatives considered

  • Making the legacy procs true facades that construct transient event objects while preserving their dict-passing contract - rejected: zero live callers make the reconstitution ceremony pointless; error-with-pointer shims are honest and cheaper.
  • Locking/concurrency first, refactor later - rejected: the safety measures would have to be implemented and kept in sync across three lifecycle implementations; sequencing the unification first means G-095 lands once.

Notes

  • Prerequisite for G-095 (concurrent-writer safety) - that goal's lock, atomic-rename and merge-on-flush measures assume this goal's single save chokepoint.
  • Version-skew during rollout: bootsupport punkcheck (0.4.0) and the dev module can both write the same tree's .punkcheck files (make.tcl runs bootsupport's copy; a dev shell runs the built one) - re-vendor bootsupport promptly after landing, and sequence this refactor before any on-disk protocol change from G-095.
  • G-087 (archived - goals/archive/G-087-thin-project-layouts.md) recorded a punkcheck MAX_PATH candidate (deep relative source chains exceed Windows path limits, unnormalized) - natural rider on this goal's load/save consolidation. Reconfirmed 2026-07-19 during the stage-4 verification: project generation into a deep directory fails reading the layout store manifests through a long ../ chain.
  • The characterization suites were written expressly to lock behaviour "before any refactoring" - this is that refactoring.
  • punkcheck::cli (status/paths) is a read-only consumer and stays untouched.

Progress

  • 2026-07-21 landed in full (punkcheck 0.5.0; punk::mix::cli 0.5.1 comment-only cleanup; bootsupport re-vendored same day): installtrack/installevent as the sole lifecycle implementation; per-event persistence policy (start_event ?-persistence eager|deferred?; deferred = in-memory working recordset, persisted by an explicit $installer flush; QUERY never persisted in either mode); punkcheck::install rebuilt as a tree-walking OO consumer in deferred mode; legacy proc pipeline (installfile_begin/started_install/finished_install/ skipped_install AND start_installer_event) retired to error-with-pointer shims with installfile_help rewritten for the OO lifecycle; single save chokepoint = installtrack method save_working_recordset (the G-095 attachment point); dead targetset class removed.
  • Acceptance verification (2026-07-21, native Tcl 9.0.3 tclsh, Windows 11):
    • install.test 35/35, installtrack.test 29/29. One pin flipped, recorded in-file: the full-lifecycle test's $event end + $installer end_event double-end now correctly errors, because ending clears the installtrack's active-event slot (the mechanism that makes sequential events on one instance possible). No real consumer double-ends (cli/make.tcl/commandsets all call $event end once - verified by grep).
    • lifecycle.test (new, 14 tests): batch/OO field equivalence for INSTALL-RECORD and SOURCE records incl equal -targets_cksums; o_record_list coherence (event-1 targetset writes survive the second start_event; event ts_begin preserved through flush); deferred no-write-until-flush and unflushed-discard semantics; QUERY leaves no on-disk FILEINFO; all five retirement shims error with pointer; get_recordlist and targetset_dict fixes.
    • Retired-trio grep over src/modules, src/make.tcl, src/project_layouts: only the module's own shims match.
    • Full build: make.tcl modules x2 plus a full make.tcl project under the re-vendored bootsupport punkcheck 0.5.0 complete exit 0 with skip/copy decisions unchanged against pre-refactor .punkcheck state - copied: 0 on every batch phase (modules, modules_tcl8/9, libs, vfs syncs, layout syncs), kit installs skip 'no change detected'; the sole punk905 kit rebuild traces to the newly promoted tclsh905 runtime (a genuinely changed source from the parallel G-102 session, not a spurious recopy).
  • Additional latent fixes surfaced by the unification (beyond the two named in Context): installtrack as_record wrote -name with a trailing space via a stray escaped-space backslash, breaking by-name installer lookup after any flush/save_installer_record; event reconstruction in the installtrack constructor passed raw record fields so -tsbegin/-tsend fell to defaults (a flush would have rewritten prior event history timestamps with 'now' and empty ends). Both fixed and pinned by lifecycle.test.
  • Deliberate contract deltas beyond the headline unification (all recorded in the punkcheck 0.5.0 buildversion changelog): QUERY targetset_started now sets -metadata_us like other operations; all-targets batch installs now store -targets_cksums (previously only non-all-targets copies did - enables later synced-targets use); batch install records gain -metadata_us/-ts_start_transfer/-transfer_us/-cksum_us (field parity with OO records); recordlist::extract_or_create_fileset_record no longer prints to stdout (isnew returned; targetset_init reports via debugchannel, and punkcheck::install preserves its progresschannel-gated announcement); an explicitly empty -punkcheck-folder now errors early instead of failing later in path relativization; -punkcheck-records is accepted-but-ignored (pre-G-094 behaviour already replaced it at depth 0); the installedsourcechanged copy print no longer emits a duplicate '(cksum time)' using the copy timing.
  • Observed during verification (G-095 evidence): one transient save failure (targetset_init -> save_working_recordset traceback) on the first post-re-vendor build run, with a second session concurrently active in the same checkout; not reproducible - two subsequent full runs clean, exit 0. Exactly the concurrent-writer exposure G-095's locking/atomic-rename measures target, now funnelled through the single chokepoint.