Browse Source
The kit machinery already knew when it had failed to extract anything from a source
runtime, and under G-122 that notice became a recapped BUILD-WARNING - but the build
still assembled an artifact, DELETED the deployed kit and copied the new one over it,
closing running instances of the kit first. Field-observed 2026-07-26: an msys2-hosted
bake of punk91 produced a 49,501,792-byte bin/punk91.exe that failed at startup with
"application-specific initialization failed: Cannot find a usable init.tcl", by which
point the previously working kit was already gone. G-124 removed the particular cause;
this removes the class. A build tool should not make a working executable worse.
A bake now refuses any kit whose merged .vfs supplies no tcl library: the kit is listed
under FAILED KITS with a reason naming the cause and NOTHING is written - no
src/_build/<kit>, no deploy, and the previously deployed bin/<kit> is left byte-identical.
The gate sits immediately after the _vfscommon.vfs + <kit>.vfs merges and before the
kit-type assembly switch, the last point at which no build product exists; a refusal
appends to failed_kits, ends the punkcheck event FAILED and continues, so the punkcheck
records stay consistent with what is on disk. The merged src/_build/<kit>.vfs tree is
deliberately left behind - it is the evidence for the refusal.
It gates on the BOOT PRECONDITION, not on the extraction step having run. Some .vfs
folders legitimately supply their own tcl library (src/vfs/punk8_statictwapi.vfs,
punk9test.vfs and mkzipfix.vfs do today), and those must keep building - so the check
reads the merged tree. It is structural and executes nothing, which also makes it valid
for cross-target kits this host could never run, and costs 0.63ms per kit (measured over
200 iterations against the real src/_build/punk905.exe.vfs), so it runs unconditionally.
The predicate is punkboot::utils::vfs_boot_library_report (0.2.0 -> 0.3.0), called through
a guarded require modelled on the existing get_src_provenance_warnings: a stale or missing
bootsupport snapshot degrades the gate to a NOTE rather than failing every kit. Both
branches were observed live - before the bootsupport propagation 'make.tcl check' reported
UNAVAILABLE, after it ACTIVE. Scope was extended at activation (user-approved) to put the
predicate in that module rather than private to the build script, because a gate that
cannot be exercised cannot be characterized, and punkboot::utils is already where
make.tcl's provenance gate helper lives.
What qualifies as a tcl library: tcl_library/init.tcl (zipfs-attached kits) or
lib/tcl<major>.<minor>/init.tcl (starkit-style), AND at least one companion file beside it
(tm.tcl, package.tcl, auto.tcl, clock.tcl, history.tcl, word.tcl) or an encoding/
directory. The companion test is load-bearing: every punkshell kit carries
lib/BWidget1.10.1/init.tcl, so a check that looked for any init.tcl anywhere would pass a
kit with no tcl library at all. lib/tcllib2.0 is correctly not matched by the lib/tcl[0-9]*
glob. Verified against all 10 assembled trees in src/_build - both conventions are
represented (tcl_library for punk902z/punk905/punk91/punk9_beta/punk9bi_beta/punkmagic,
lib/tcl8.6 for punk86/punkbi/punksys, lib/tcl9.0 for punkshell902).
The no-extraction BUILD-WARNING was reworded from "will not initialise unless src/vfs/<x>
supplies one" - a consequence the build then ignored - to "will FAIL the boot-precondition
gate unless src/vfs/<x> supplies one". It stays a warning rather than becoming the failure,
because a .vfs that supplies its own library needs no extraction.
Verified 2026-07-27 end-to-end against a temporary fixture kit (a mapvfs entry pointing a
zip kit at a payload-free runtime, plus a vfs with no tcl library), with sentinel files
standing in for a previously deployed kit and a previous build product. Across the failing
run bin/punkgatefixture.exe (sha1 2a0700eb...) and src/_build/punkgatefixture.exe (sha1
12c50567...) were byte-identical with unchanged mtimes, and the kit was reported under
FAILED KITS with the cause named. Adding tcl_library/{init.tcl,tm.tcl} to that same fixture
vfs - extraction still yielding nothing - made it build and deploy normally, which is the
other half of the criterion. The fixture was removed afterwards and mapvfs.config restored
byte-identical to its backup. Real-kit bake through the gate: punk905 builds, deploys and
boots.
Characterization: 12 tests in modules/punkboot/utils/testsuites/utils/bootlibrary.test
(both conventions, the companion-file requirement and its BWidget decoy, near-miss
reporting, missing/empty trees, and a sweep asserting every assembled src/_build/*.vfs
tree still passes so the gate cannot fail kits that boot today) and 2 in
shell/testsuites/punkexe/maketclbootgate.test (the ACTIVE/UNAVAILABLE report, ESC-free per
the G-113 piped policy). Existing punkexe suites pass unchanged.
Documented in src/AGENTS.md with the remedy when a kit is refused, in ARCHITECTURE.md
beside the provenance gates, and in the embedded 'make.tcl workflow' data flow as key note
K10 (verified under the default and PUNKBOOT_PLAIN=1 paths, within the 100-column budget).
The achieved flip archives the entry and the detail file, and sweeps the live tier: G-127
(its output location inherits the never-written guarantee as long as the gate stays
upstream of the artifact write, and its payload/target mismatch reporting should share this
vocabulary), G-028 (the sibling "why bin/<kit> was not updated" surface in the same deploy
step) and G-101 (a new container type must land its library where the gate looks, or extend
the location list).
Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com
master
18 changed files with 685 additions and 94 deletions
@ -1,83 +0,0 @@
|
||||
# G-125 A kit that cannot boot is not deployed |
||||
|
||||
Status: proposed |
||||
Scope: src/make.tcl (kit extraction outcome handling, deploy step gating, the no-extraction BUILD-WARNING as the current behaviour being replaced); src/tests/shell/testsuites/punkexe/ (characterization of the gate); src/AGENTS.md (bake failure-mode documentation) |
||||
Goal: a bake that could not lift a runtime's own payload (its tcl_library) into the kit fails that kit instead of building and deploying an artifact that cannot boot - so the worst outcome of a missing extraction capability is a red kit, never a silently replaced working executable. |
||||
Acceptance: when extraction from the source runtime yields nothing and the kit's .vfs does not itself supply a bootable tcl library, the kit is recorded in FAILED KITS with a reason naming the cause and NEITHER src/_build nor bin receives a new artifact - the previously deployed kit is left untouched, verified by mtime and byte comparison across such a run; a kit whose .vfs legitimately supplies its own tcl library still builds and deploys, so the gate tests for the boot precondition rather than for the extraction step having run; the check is cheap enough to run on every kit and executes no artifact; the condition and its remedy are documented in src/AGENTS.md; existing punkexe suites pass unchanged and the gate itself is characterized. |
||||
|
||||
## Context |
||||
|
||||
The kit machinery already knows when it failed to extract anything from a source runtime. |
||||
Under G-122 (achieved - see goals/archive/G-122-host-target-platform-split.md) that |
||||
notice was promoted from an inline stderr block to a recapped `BUILD-WARNING:` naming the |
||||
kit and what was tried - but the build still proceeds, assembles an artifact, DELETES the |
||||
deployed kit and copies the new one over it. The pre-deploy process sweep will even close |
||||
running instances of the kit first. |
||||
|
||||
Field-observed 2026-07-26: an msys2-hosted bake of `punk91` produced a 49,501,792-byte |
||||
`bin/punk91.exe` that failed at startup with |
||||
|
||||
application-specific initialization failed: Cannot find a usable init.tcl |
||||
|
||||
because the host's tclsh had no tcllib and therefore could not extract the runtime's own |
||||
zip. The previously working kit had already been replaced by then. G-124 removes the |
||||
particular cause, but not the class: a runtime with no attached payload at all, an |
||||
extraction that fails for any other reason, or a future container type reaches the same |
||||
state, and the build's response is still "warn and deploy". |
||||
|
||||
The existing precedent in the same code path is `vfs_startup_script_warning`, which warns |
||||
about a missing `main.tcl` and continues - deliberately, because a kit without a startup |
||||
script is legal. A kit without a resolvable tcl library is not in the same category: it |
||||
cannot initialise at all. |
||||
|
||||
## Approach |
||||
|
||||
- Gate on the BOOT PRECONDITION, not on the extraction step having run. Some .vfs folders |
||||
legitimately supply their own tcl library (the merged `targetvfs` is what matters), so |
||||
the check inspects the assembled tree rather than the extraction outcome - a runtime |
||||
that needed no extraction must keep building. |
||||
- Fail the kit the same way every other failure in that loop does (append to |
||||
`failed_kits` with a reason, end the punkcheck event FAILED) BEFORE the artifact is |
||||
renamed into place, so neither `src/_build/<kit>` nor `bin/<kit>` is touched and the |
||||
punkcheck records stay consistent with what is on disk. |
||||
- Keep it cheap and non-executing: a structural check of the merged vfs, no artifact |
||||
launch, so it can run for every kit on every bake. |
||||
- The recapped BUILD-WARNING wording changes from "will not initialise unless the vfs |
||||
supplies one" to a failure reason, since the gate now enforces what the warning |
||||
described. |
||||
|
||||
## Alternatives considered |
||||
|
||||
- Keep warning and deploying, on the grounds that the developer sees the recap - rejected: |
||||
the recap arrives after the deployed kit has already been replaced, and the failure |
||||
surfaces later as an unexplained broken shell. A build tool should not make a working |
||||
executable worse. |
||||
- Boot the freshly built artifact as the check - rejected: it cannot work for |
||||
cross-target kits (G-122 makes those routine), costs a process launch per kit, and a |
||||
kit that boots into an interactive shell is awkward to probe safely. |
||||
- Emit to `src/_build` but refuse only the deploy - rejected as a half-measure that |
||||
leaves a punkcheck record claiming a good build product; a later run would consider it |
||||
current. |
||||
|
||||
## Notes |
||||
|
||||
- Related: G-124 (achieved 2026-07-26 - see goals/archive/G-124-punkzip-reader.md) - removed |
||||
the most common cause: a tcllib-less tclsh can now read a runtime's attached zip, and a |
||||
bake from msys2's tclsh8.6 carries `tcl_library` into the kit instead of warning past it. |
||||
This gate is independent of that and remains valuable: the class survives (a runtime with |
||||
no attached payload, an extraction that fails for any other reason, a future container |
||||
type), and the build's response to it is still "warn and deploy". Note for the gate's |
||||
design: the reader now RAISES with a named reason (encrypted, zip64, unknown compression |
||||
method, crc mismatch, offsets that do not describe the file) where the old path returned |
||||
an opaque tcllib error, so the gate has a usable failure reason to report. |
||||
- Related: G-122 (achieved) - promoted the no-extraction notice to a recapped |
||||
BUILD-WARNING and recorded the field incident that motivates this goal; see |
||||
goals/archive/G-122-host-target-platform-split.md. |
||||
- Related: G-101 - a new 8.6 container type is exactly the situation where an extraction |
||||
path can silently produce nothing; the gate is what keeps that honest. |
||||
- Related: G-028 - sibling failure-reporting surface in the same deploy step (that goal |
||||
names the process holding a target the build cannot replace; this one refuses to |
||||
replace a target with something unbootable). |
||||
- Overlap survey also considered G-057 (icon embedding rides the same wrap steps) and |
||||
G-115 (declarative .vfs composition decides what the vfs supplies) - neither is a |
||||
dependency: this gate reads whatever the merged tree contains, however it was composed. |
||||
@ -0,0 +1,157 @@
|
||||
# G-125 A kit that cannot boot is not deployed |
||||
|
||||
Status: achieved 2026-07-27 |
||||
Scope: src/make.tcl (kit extraction outcome handling, deploy step gating, the no-extraction BUILD-WARNING as the current behaviour being replaced); src/modules/punkboot/utils-999999.0a1.0.tm (the boot-precondition predicate, called guardedly from make.tcl as vcs_dirty_warnings is); src/tests/modules/punkboot/utils/ (predicate characterization over directory fixtures); src/tests/shell/testsuites/punkexe/ (characterization of the make.tcl-side gate reporting); src/AGENTS.md (bake failure-mode documentation) |
||||
Goal: a bake that could not lift a runtime's own payload (its tcl_library) into the kit fails that kit instead of building and deploying an artifact that cannot boot - so the worst outcome of a missing extraction capability is a red kit, never a silently replaced working executable. |
||||
Acceptance: when extraction from the source runtime yields nothing and the kit's .vfs does not itself supply a bootable tcl library, the kit is recorded in FAILED KITS with a reason naming the cause and NEITHER src/_build nor bin receives a new artifact - the previously deployed kit is left untouched, verified by mtime and byte comparison across such a run; a kit whose .vfs legitimately supplies its own tcl library still builds and deploys, so the gate tests for the boot precondition rather than for the extraction step having run; the check is cheap enough to run on every kit and executes no artifact; the condition and its remedy are documented in src/AGENTS.md; existing punkexe suites pass unchanged and the gate itself is characterized. |
||||
|
||||
## Context |
||||
|
||||
The kit machinery already knows when it failed to extract anything from a source runtime. |
||||
Under G-122 (achieved - see goals/archive/G-122-host-target-platform-split.md) that |
||||
notice was promoted from an inline stderr block to a recapped `BUILD-WARNING:` naming the |
||||
kit and what was tried - but the build still proceeds, assembles an artifact, DELETES the |
||||
deployed kit and copies the new one over it. The pre-deploy process sweep will even close |
||||
running instances of the kit first. |
||||
|
||||
Field-observed 2026-07-26: an msys2-hosted bake of `punk91` produced a 49,501,792-byte |
||||
`bin/punk91.exe` that failed at startup with |
||||
|
||||
application-specific initialization failed: Cannot find a usable init.tcl |
||||
|
||||
because the host's tclsh had no tcllib and therefore could not extract the runtime's own |
||||
zip. The previously working kit had already been replaced by then. G-124 removes the |
||||
particular cause, but not the class: a runtime with no attached payload at all, an |
||||
extraction that fails for any other reason, or a future container type reaches the same |
||||
state, and the build's response is still "warn and deploy". |
||||
|
||||
The existing precedent in the same code path is `vfs_startup_script_warning`, which warns |
||||
about a missing `main.tcl` and continues - deliberately, because a kit without a startup |
||||
script is legal. A kit without a resolvable tcl library is not in the same category: it |
||||
cannot initialise at all. |
||||
|
||||
## Approach |
||||
|
||||
- Gate on the BOOT PRECONDITION, not on the extraction step having run. Some .vfs folders |
||||
legitimately supply their own tcl library (the merged `targetvfs` is what matters), so |
||||
the check inspects the assembled tree rather than the extraction outcome - a runtime |
||||
that needed no extraction must keep building. |
||||
- Fail the kit the same way every other failure in that loop does (append to |
||||
`failed_kits` with a reason, end the punkcheck event FAILED) BEFORE the artifact is |
||||
renamed into place, so neither `src/_build/<kit>` nor `bin/<kit>` is touched and the |
||||
punkcheck records stay consistent with what is on disk. |
||||
- Keep it cheap and non-executing: a structural check of the merged vfs, no artifact |
||||
launch, so it can run for every kit on every bake. |
||||
- The predicate lives in `punkboot::utils` and make.tcl calls it through a guarded require, |
||||
exactly as it already does for `vcs_dirty_warnings` - Scope extended at activation |
||||
(2026-07-27, user-approved) because a predicate private to the build script cannot be |
||||
characterized, and that module is the established home for make.tcl's gate helpers. |
||||
- The recapped BUILD-WARNING wording changes from "will not initialise unless the vfs |
||||
supplies one" to a failure reason, since the gate now enforces what the warning |
||||
described. |
||||
|
||||
## Alternatives considered |
||||
|
||||
- Keep warning and deploying, on the grounds that the developer sees the recap - rejected: |
||||
the recap arrives after the deployed kit has already been replaced, and the failure |
||||
surfaces later as an unexplained broken shell. A build tool should not make a working |
||||
executable worse. |
||||
- Boot the freshly built artifact as the check - rejected: it cannot work for |
||||
cross-target kits (G-122 makes those routine), costs a process launch per kit, and a |
||||
kit that boots into an interactive shell is awkward to probe safely. |
||||
- Emit to `src/_build` but refuse only the deploy - rejected as a half-measure that |
||||
leaves a punkcheck record claiming a good build product; a later run would consider it |
||||
current. |
||||
|
||||
## Notes |
||||
|
||||
- Related: G-124 (achieved 2026-07-26 - see goals/archive/G-124-punkzip-reader.md) - removed |
||||
the most common cause: a tcllib-less tclsh can now read a runtime's attached zip, and a |
||||
bake from msys2's tclsh8.6 carries `tcl_library` into the kit instead of warning past it. |
||||
This gate is independent of that and remains valuable: the class survives (a runtime with |
||||
no attached payload, an extraction that fails for any other reason, a future container |
||||
type), and the build's response to it is still "warn and deploy". Note for the gate's |
||||
design: the reader now RAISES with a named reason (encrypted, zip64, unknown compression |
||||
method, crc mismatch, offsets that do not describe the file) where the old path returned |
||||
an opaque tcllib error, so the gate has a usable failure reason to report. |
||||
- Related: G-122 (achieved) - promoted the no-extraction notice to a recapped |
||||
BUILD-WARNING and recorded the field incident that motivates this goal; see |
||||
goals/archive/G-122-host-target-platform-split.md. |
||||
- Related: G-127 (recorded at activation 2026-07-26 - drafted after this goal, and the |
||||
activation-freshness survey's top unlinked pair on shared paths: src/make.tcl deploy |
||||
step, src/tests/shell/testsuites/punkexe/, src/AGENTS.md). It moves non-native kits to |
||||
bin/kits/<platform>/ and adds its own payload/target mismatch reporting, so this gate |
||||
must refuse before whichever output location that goal settles on, and the two failure |
||||
reports should read as one vocabulary rather than two. |
||||
- Related: G-101 - a new 8.6 container type is exactly the situation where an extraction |
||||
path can silently produce nothing; the gate is what keeps that honest. |
||||
- Related: G-028 - sibling failure-reporting surface in the same deploy step (that goal |
||||
names the process holding a target the build cannot replace; this one refuses to |
||||
replace a target with something unbootable). |
||||
- Overlap survey also considered G-057 (icon embedding rides the same wrap steps) and |
||||
G-115 (declarative .vfs composition decides what the vfs supplies) - neither is a |
||||
dependency: this gate reads whatever the merged tree contains, however it was composed. |
||||
|
||||
## Progress |
||||
|
||||
2026-07-27 - implemented; every Acceptance clause verified. Detail: |
||||
|
||||
- `punkboot::utils` 0.2.0 -> 0.3.0 gains `vfs_boot_library_report {vfsfolder}`, returning |
||||
`ok` / `locations` / `rejected` / `checked` / `reason`. make.tcl calls it through |
||||
`::punkboot::get_vfs_boot_library_report`, a guarded require modelled on the existing |
||||
`get_src_provenance_warnings`, so a stale bootsupport snapshot degrades the gate to a |
||||
NOTE rather than failing every kit. Both branches were observed live: before the |
||||
bootsupport propagation `make.tcl check` reported UNAVAILABLE, after it ACTIVE. |
||||
- The gate sits in the kit loop immediately after the `_vfscommon.vfs` + `<kit>.vfs` |
||||
merges and before the kit-type assembly switch - the last point at which no build |
||||
product exists. A refusal appends to `failed_kits`, ends the punkcheck event FAILED and |
||||
`continue`s, so `src/_build/<kit>` and `bin/<kit>` are never touched and the punkcheck |
||||
records stay consistent with what is on disk. Note the merged `src/_build/<kit>.vfs` |
||||
tree IS left behind (it is the evidence for the refusal); only the artifact is refused. |
||||
- What qualifies as a tcl library: `tcl_library/init.tcl` (zipfs-attached kits) or |
||||
`lib/tcl<major>.<minor>/init.tcl` (starkit-style), AND at least one companion file |
||||
(`tm.tcl`, `package.tcl`, `auto.tcl`, `clock.tcl`, `history.tcl`, `word.tcl`) or an |
||||
`encoding/` directory beside it. The companion test is load-bearing: every punkshell kit |
||||
carries `lib/BWidget1.10.1/init.tcl` and `lib/BWidget1.9.16/init.tcl`, so a check that |
||||
looked for any `init.tcl` would pass a kit with no tcl library at all. `lib/tcllib2.0` |
||||
is correctly not matched by the `lib/tcl[0-9]*` glob. |
||||
- The no-extraction BUILD-WARNING was reworded from "will not initialise unless |
||||
src/vfs/<x> supplies one" (which described a consequence the build then ignored) to |
||||
"will FAIL the boot-precondition gate unless src/vfs/<x> supplies one". It stays a |
||||
warning rather than becoming the failure, because a .vfs that supplies its own library |
||||
needs no extraction - the gate decides, on the merged tree. |
||||
|
||||
### Verification records (2026-07-27) |
||||
|
||||
- Predicate over the 10 assembled kit trees in `src/_build`: all pass, and the two |
||||
conventions are both represented (tcl_library for punk902z/punk905/punk91/punk9_beta/ |
||||
punk9bi_beta/punkmagic, lib/tcl8.6 for punk86/punkbi/punksys, lib/tcl9.0 for |
||||
punkshell902). Of the source `.vfs` folders three legitimately supply their own library |
||||
(mkzipfix.vfs, punk8_statictwapi.vfs, punk9test.vfs) - the case the gate must not break. |
||||
- Gate fired end-to-end against a temporary fixture kit (a mapvfs entry pointing a zip kit |
||||
at a payload-free runtime plus a vfs with no tcl library), with sentinel files standing |
||||
in for a previously deployed kit and a previous build product: |
||||
`bin/punkgatefixture.exe` sha1 2a0700ebfad097e34f70f6d779c8c1c8f2ba7dba and |
||||
`src/_build/punkgatefixture.exe` sha1 12c505679ae7cc8fe44527eb4e8f34fa2659894d were |
||||
byte-identical with unchanged mtimes across the failing run, and the kit was reported |
||||
under FAILED KITS with the cause named. Adding `tcl_library/{init.tcl,tm.tcl}` to that |
||||
same fixture vfs - extraction still yielding nothing - made the kit build and deploy |
||||
normally (INSTALLED KITS: 1), which is the "tests the precondition, not the extraction |
||||
step" half of the criterion. The fixture (mapvfs entry, vfs folder, fake runtime, |
||||
sentinels, build leftovers) was removed afterwards and mapvfs.config restored |
||||
byte-identical to its backup. |
||||
- Real-kit bake through the gate: `bake -confirm 0 punk905` builds, deploys and boots |
||||
(tcl_library at //zipfs:/app/tcl_library, punkboot::utils 0.3.0 present in the kit). |
||||
- `src/tests/modules/punkboot/utils/testsuites/utils/bootlibrary.test` 12/12; |
||||
`src/tests/shell/testsuites/punkexe/maketclbootgate.test` 2/2. |
||||
- `make.tcl workflow` renders the gate in the kit data flow with key note K10, verified |
||||
under the default and PUNKBOOT_PLAIN=1 paths, max line width within the 100 budget. |
||||
- Cost: 0.63ms per kit measured over 200 iterations against the real |
||||
`src/_build/punk905.exe.vfs` tree (a `glob` plus a handful of `file isfile` tests, no |
||||
recursion, nothing executed) - negligible beside a bake that takes minutes, which is |
||||
what lets it run for every kit unconditionally. |
||||
- Full source-tree suite after the change: 1161 tests, 1138 passed, 20 skipped, 3 failed. |
||||
One is `core/tcl exec-14.3`, the documented pre-existing baseline. The other two are in |
||||
`modules/argparsingtest/` and belong to unrelated work in progress in the working tree |
||||
at the time of the run (that module, its buildversion and both of its test files were |
||||
being edited concurrently); nothing in this change reaches arg-parsing benchmarks. |
||||
@ -1,4 +1,4 @@
|
||||
[project] |
||||
name = "punkshell" |
||||
version = "0.26.0" |
||||
version = "0.27.0" |
||||
license = "BSD-2-Clause" |
||||
|
||||
@ -1,6 +1,7 @@
|
||||
0.2.0 |
||||
0.3.0 |
||||
#First line must be a semantic version number |
||||
#all other lines are ignored. |
||||
#0.3.0 - added vfs_boot_library_report (structural boot-precondition check of an assembled .vfs tree; backs make.tcl's G-125 unbootable-kit deploy gate) |
||||
#0.2.0 - vcs_dirty_warnings: optional scope arg limiting which uncommitted changes count (e.g scope src for make.tcl build/promotion dirty-src gate) |
||||
#0.1.1 - added vcs_dirty_warnings (dirty fossil/git checkout provenance warnings; used by make.tcl vendorupdate, shared with bootsupport update per goal G-026) |
||||
#0.1.0 - initial: parse_punkproject_version, read_punkproject_version, read_changelog_latest_version extracted from make.tcl inline procs |
||||
|
||||
@ -0,0 +1,166 @@
|
||||
# -*- tcl -*- |
||||
# Tests for punkboot::utils::vfs_boot_library_report - the structural boot-precondition |
||||
# check behind make.tcl's G-125 unbootable-kit deploy gate: |
||||
# - both tcl-library conventions this project's kits use (tcl_library/ for zipfs-attached |
||||
# kits, lib/tcl<major>.<minor>/ for starkit-style kits) |
||||
# - the companion-file requirement that keeps a package's own init.tcl (every punkshell |
||||
# kit carries lib/BWidget1.10.1/init.tcl) from answering for a tcl library |
||||
# - near-miss reporting, missing/empty trees, and the real built vfs trees in src/_build |
||||
# when a build has been run on this checkout |
||||
# Directory fixtures only - the check never executes or launches anything, which is what |
||||
# makes it usable for cross-target kits. |
||||
# Run: tclsh src/tests/runtests.tcl -report compact -show-passes 0 -include-paths modules/punkboot/utils/*** bootlibrary.test |
||||
|
||||
package require tcltest |
||||
package require punkboot::utils |
||||
|
||||
#added 2026-07-27 (agent, G-125) |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
|
||||
variable BASE [makeDirectory g125_bootlibrary] |
||||
|
||||
#<projectroot>/src/tests/modules/punkboot/utils/testsuites/utils -> 7 levels up |
||||
variable projectroot [file normalize [file join [file dirname [info script]] .. .. .. .. .. .. ..]] |
||||
variable buildfolder [file join $projectroot src _build] |
||||
testConstraint builtvfsavailable [expr {[llength [glob -nocomplain -type d -directory $buildfolder *.vfs]] > 0}] |
||||
|
||||
proc mkfile {path} { |
||||
file mkdir [file dirname $path] |
||||
set fd [open $path w] |
||||
puts -nonewline $fd "#fixture\n" |
||||
close $fd |
||||
} |
||||
#build a fresh fixture tree under BASE and return its path |
||||
proc mktree {name args} { |
||||
variable BASE |
||||
set root [file join $BASE $name] |
||||
file delete -force $root |
||||
file mkdir $root |
||||
foreach rel $args { |
||||
if {[string index $rel end] eq "/"} { |
||||
file mkdir [file join $root [string trimright $rel /]] |
||||
} else { |
||||
mkfile [file join $root $rel] |
||||
} |
||||
} |
||||
return $root |
||||
} |
||||
proc report {root} { |
||||
return [punkboot::utils::vfs_boot_library_report $root] |
||||
} |
||||
|
||||
variable common { |
||||
set result [list] |
||||
} |
||||
|
||||
# -- --- --- the two conventions --- --- -- |
||||
|
||||
test bootlib_zipfs_convention {a zipfs-style tcl_library with a companion file qualifies}\ |
||||
-setup $common -body { |
||||
set r [report [mktree zipfs tcl_library/init.tcl tcl_library/tm.tcl main.tcl]] |
||||
lappend result [dict get $r ok] [dict get $r locations] [dict get $r rejected] [dict get $r reason] |
||||
}\ |
||||
-result {1 tcl_library {} {}} |
||||
|
||||
test bootlib_starkit_convention {a starkit-style lib/tcl<major>.<minor> with a companion file qualifies}\ |
||||
-setup $common -body { |
||||
set r [report [mktree starkit lib/tcl8.6/init.tcl lib/tcl8.6/package.tcl main.tcl]] |
||||
lappend result [dict get $r ok] [dict get $r locations] |
||||
}\ |
||||
-result {1 lib/tcl8.6} |
||||
|
||||
test bootlib_encoding_directory_qualifies {an encoding/ subdirectory beside init.tcl is accepted in place of a companion file}\ |
||||
-setup $common -body { |
||||
set r [report [mktree encdir tcl_library/init.tcl tcl_library/encoding/]] |
||||
lappend result [dict get $r ok] [dict get $r locations] |
||||
}\ |
||||
-result {1 tcl_library} |
||||
|
||||
test bootlib_both_conventions {a tree carrying both conventions reports both, tcl_library first}\ |
||||
-setup $common -body { |
||||
set r [report [mktree both tcl_library/init.tcl tcl_library/tm.tcl lib/tcl9.0/init.tcl lib/tcl9.0/auto.tcl]] |
||||
lappend result [dict get $r ok] [dict get $r locations] |
||||
}\ |
||||
-result {1 {tcl_library lib/tcl9.0}} |
||||
|
||||
test bootlib_reports_checked_locations {the report names the locations it examined whether or not it found one}\ |
||||
-setup $common -body { |
||||
lappend result [dict get [report [mktree checked1 main.tcl]] checked] |
||||
lappend result [dict get [report [mktree checked2 tcl_library/init.tcl tcl_library/tm.tcl]] checked] |
||||
}\ |
||||
-result {{tcl_library lib/tcl<M>.<m>} {tcl_library lib/tcl<M>.<m>}} |
||||
|
||||
# -- --- --- what must NOT qualify --- --- -- |
||||
|
||||
test bootlib_package_init_decoy {a package's own init.tcl under lib/ does not answer for a tcl library}\ |
||||
-setup $common -body { |
||||
#every punkshell kit carries these - they are the reason the check cannot |
||||
#simply look for an init.tcl anywhere in the tree |
||||
set r [report [mktree decoy lib/BWidget1.10.1/init.tcl lib/BWidget1.9.16/init.tcl main.tcl modules/]] |
||||
lappend result [dict get $r ok] [dict get $r locations] [dict get $r rejected] |
||||
lappend result [string match "*neither tcl_library/init.tcl nor lib/tcl<major>.<minor>/init.tcl*" [dict get $r reason]] |
||||
}\ |
||||
-result {0 {} {} 1} |
||||
|
||||
test bootlib_tcllib_is_not_a_tcl_library {lib/tcllib2.0 is not matched by the lib/tcl<major>.<minor> convention}\ |
||||
-setup $common -body { |
||||
set r [report [mktree tcllib lib/tcllib2.0/init.tcl lib/tcllib2.0/tm.tcl]] |
||||
lappend result [dict get $r ok] [dict get $r rejected] |
||||
}\ |
||||
-result {0 {}} |
||||
|
||||
test bootlib_near_miss_is_named {init.tcl in the right place with no companion is rejected and reported as a near miss}\ |
||||
-setup $common -body { |
||||
set r [report [mktree nearmiss tcl_library/init.tcl]] |
||||
lappend result [dict get $r ok] [dict get $r locations] [dict get $r rejected] |
||||
lappend result [string match "*hold init.tcl but none of the companion files*" [dict get $r reason]] |
||||
}\ |
||||
-result {0 {} tcl_library 1} |
||||
|
||||
test bootlib_empty_tree {a tree with no tcl library at all is refused with a reason}\ |
||||
-setup $common -body { |
||||
set r [report [mktree emptytree]] |
||||
lappend result [dict get $r ok] [expr {[dict get $r reason] ne ""}] |
||||
}\ |
||||
-result {0 1} |
||||
|
||||
test bootlib_missing_directory {a nonexistent folder is refused by name rather than erroring}\ |
||||
-setup $common -body { |
||||
variable BASE |
||||
set r [report [file join $BASE no_such_vfs]] |
||||
lappend result [dict get $r ok] [string match "no such directory:*" [dict get $r reason]] |
||||
}\ |
||||
-result {0 1} |
||||
|
||||
test bootlib_does_not_execute_or_write {the check writes nothing into the tree it inspects}\ |
||||
-setup $common -body { |
||||
set root [mktree readonlycheck tcl_library/init.tcl tcl_library/tm.tcl main.tcl] |
||||
set before [lsort [glob -nocomplain -directory $root -tails *]] |
||||
report $root |
||||
set after [lsort [glob -nocomplain -directory $root -tails *]] |
||||
lappend result [expr {$before eq $after}] $before |
||||
}\ |
||||
-result {1 {main.tcl tcl_library}} |
||||
|
||||
# -- --- --- the trees the gate will actually see --- --- -- |
||||
|
||||
test bootlib_real_built_vfs_trees {every assembled kit vfs in src/_build passes the gate - the check must not fail kits that boot today}\ |
||||
-constraints {builtvfsavailable} -setup $common -body { |
||||
variable buildfolder |
||||
set failures [list] |
||||
set checked 0 |
||||
foreach d [lsort [glob -nocomplain -type d -directory $buildfolder *.vfs]] { |
||||
incr checked |
||||
set r [report $d] |
||||
if {![dict get $r ok]} { |
||||
lappend failures [file tail $d] |
||||
} |
||||
} |
||||
lappend result [expr {$checked > 0}] $failures |
||||
}\ |
||||
-result {1 {}} |
||||
|
||||
cleanupTests |
||||
} |
||||
@ -0,0 +1,124 @@
|
||||
package require tcltest |
||||
|
||||
#Piped characterization of the make.tcl side of the G-125 boot-precondition gate: a bake |
||||
#must refuse a kit whose merged vfs supplies no tcl library, rather than replacing a |
||||
#working bin/<kit> with an artifact that cannot initialise. Runs the WORKING TREE's |
||||
#src/make.tcl under the built punk executable's 'script' subcommand with output captured |
||||
#through a pipe, and pins: |
||||
# - 'check' reports the gate as ACTIVE when punkboot::utils in bootsupport carries |
||||
# vfs_boot_library_report, naming both tcl-library conventions and the never-deployed |
||||
# guarantee; the UNAVAILABLE branch is the documented degradation when the bootsupport |
||||
# snapshot is stale (same guarded-require treatment as the dirty-src provenance check) |
||||
# - the report stays ESC-free under the G-113 piped colour policy |
||||
#The gate's VERDICT - which trees pass and which are refused - is characterized directly |
||||
#against directory fixtures in modules/punkboot/utils/testsuites/utils/bootlibrary.test; |
||||
#the predicate lives in punkboot::utils precisely so it can be tested without a bake. |
||||
# |
||||
#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)] |
||||
} |
||||
|
||||
#added 2026-07-27 (agent, G-125) |
||||
test maketcl_check_bootgate_status {make.tcl check reports the boot-precondition gate with one of the two documented statuses, ESC-free}\ |
||||
-constraints {punkexeavailable} -body { |
||||
set r [maketcl_run {check}] |
||||
set out [dict get $r output] |
||||
set result [list] |
||||
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode] |
||||
lappend result esc [regexp -all {\x1b} $out] |
||||
lappend result line [regexp -line {^boot-precondition gate \(G-125\): (ACTIVE|UNAVAILABLE)} $out] |
||||
set result |
||||
}\ |
||||
-result {timedout 0 exitcode 0 esc 0 line 1} |
||||
|
||||
#added 2026-07-27 (agent, G-125) |
||||
test maketcl_check_bootgate_active_contract {with a current bootsupport snapshot the gate is ACTIVE and states both conventions and the never-deployed guarantee}\ |
||||
-constraints {punkexeavailable} -body { |
||||
set r [maketcl_run {check}] |
||||
set out [dict get $r output] |
||||
set result [list] |
||||
lappend result active [regexp -line {^boot-precondition gate \(G-125\): ACTIVE\s*$} $out] |
||||
#the two tcl-library conventions the gate accepts |
||||
lappend result conventions [regexp {tcl_library/ or lib/tcl<major>\.<minor>/} $out] |
||||
#the contract that motivates the goal: refused kits are neither built nor deployed |
||||
lappend result failedkits [regexp {FAILED KITS} $out] |
||||
lappend result neverreplaced [regexp {never replaced by an artifact that cannot initialise} $out] |
||||
set result |
||||
}\ |
||||
-result {active 1 conventions 1 failedkits 1 neverreplaced 1} |
||||
|
||||
cleanupTests |
||||
} |
||||
namespace delete ::testspace |
||||
Loading…
Reference in new issue