diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 2f5e1c0e..3a40a74b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -84,6 +84,7 @@ Two layers with a deliberate dependency direction (the class never depends on th - **Host vs target platform (G-122).** The kit surfaces separate what the driving tclsh IS from what a bake is FOR. Target-keyed: the `bin/runtime/` store a runtime is read from, `.exe` suffixing of runtimes and kit outputs, presence checks, and the pre-deploy process sweep's tooling (`tasklist`/`taskkill` vs `ps`/`kill`, skipped when the target's processes cannot exist on this host). Host-keyed: copy commands, path handling, filesystem case rules, prompts. The default target is the host canon except for cygwin-family hosts (msys2/cygwin-runtime tclsh - `tcl_platform(platform)` `unix` on windows), which target `win32-x86_64`; a `mapvfs.config` entry may declare its own target and is then addressed in that platform's tier. `make.tcl check` prints the derivation. Zip-type kits assemble without zipfs in the driving tcl (raw-runtime split + `punk::zip::mkzip` + concatenation), and since G-124 they EXTRACT without it too: `punk::zip` reads a zip - plain, or attached to an executable under either offset convention - with stock Tcl only, so a bake needs neither zipfs nor tcllib to carry a runtime's `tcl_library` into the kit. Sources: `src/AGENTS.md`, `src/runtime/AGENTS.md`, `src/modules/punk/platform-999999.0a1.0.tm`, `src/modules/punk/zip-999999.0a1.0.tm`. - **punkcheck** records every install/delete as events in per-folder `.punkcheck` directories - the basis for skip/copy change detection, superseded-module pruning and provenance. Single OO record lifecycle (G-094, achieved) with atomic saves and advisory event-scoped locking for concurrent writers (G-095, achieved). - **Provenance gates.** Build/promotion commands warn on uncommitted `src/` changes (column-0 `PROVENANCE-WARNING:` token, `-dirty-abort` for strict mode); `vendorupdate` warns for dirty source-project checkouts. +- **Boot-precondition gate (G-125).** A bake refuses a kit whose merged `.vfs` supplies no tcl library rather than deploying an artifact that cannot initialise: the kit lands in `FAILED KITS` and nothing is written, so the previously deployed `bin/` survives. Structural and non-executing (so cross-target kits are covered), reading the merged tree rather than the extraction outcome. The predicate is `punkboot::utils::vfs_boot_library_report`, called through the same guarded require as the provenance check so a stale bootsupport snapshot degrades it to a notice; `make.tcl check` reports ACTIVE/UNAVAILABLE. Sources: `src/AGENTS.md`, `src/modules/punkboot/utils-999999.0a1.0.tm`. - **Buildsuites and the kit family.** `src/buildsuites/suite_tcl90/` builds Tcl/Tk/tcllib from source with a pinned zig toolchain and produces the runtime kit family (plain / punk / bi) plus artifact metadata (G-096-G-117 era: see archived goals G-096, G-098, G-102, G-103, G-107); artifacts publish to the punkbin repo that `bin/punk-runtime.cmd` fetches from. `src/buildsuites/suite_tcl86/` is the 8.6 sibling (G-099 + G-100, both achieved): a Tcl 8.6 windows runtime (static + dynamic shells, `tcl86t.dll`, on-disk lib tree - no zipfs, so no self-contained kit) with thread 2.8 + tclvfs + Tk 8.6 + tklib + tcllib(+tcllibc critcl accelerators) companions, gated against the 8.6 core/thread/tclvfs testsuites with tcllib/tklib on a record tier and an opt-in `test-tk`; punkshell's own runtests is censused on that runtime at parity with a same-day native-8.6 baseline. Suite child shells scrub `TCL__TM_PATH` as well as TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY - without it a census measures the machine's module trees. The 8.6 kit container strategy remains in-flux - see "In-flux areas". - **Project generation.** `dev project.new` composes thin layouts from `src/project_layouts/` (overlay chain with `.anti` deletion markers and `name@base` derivation - G-087, achieved) and injects bootsupport modules from the generating shell at generation time. - **Workflow overview.** `tclsh src/make.tcl workflow` prints the embedded ASCII data-flow overview of the build/release pipeline, with its own update contract in `src/AGENTS.md`. This section deliberately summarises rather than copies it. diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f5bd11b..332b4276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ 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.27.0] - 2026-07-27 + +- A kit that cannot boot is no longer deployed (G-125). `make.tcl bake` now checks each + kit's merged `.vfs` for a tcl library before building it; a kit with none is listed under + `FAILED KITS` with a reason naming the cause and NOTHING is written - no + `src/_build/`, no deploy, and the previously deployed `bin/` is left + byte-identical. Previously the build warned and carried on, deleting the working kit and + replacing it with an artifact that died at startup with + "application-specific initialization failed: Cannot find a usable init.tcl". +- The gate tests the boot precondition, not whether extraction ran: a `.vfs` that supplies + its own tcl library builds and deploys exactly as before. The check is structural and + executes nothing, so it covers cross-target kits this host could never run, and it is + cheap enough to run for every kit on every bake. +- `tclsh src/make.tcl check` reports the gate as ACTIVE or UNAVAILABLE, and + `tclsh src/make.tcl workflow` shows it in the kit data flow (key note K10). A stale + bootsupport snapshot degrades the gate to a NOTE rather than failing every kit - the same + guarded-require treatment the dirty-src provenance check already had. +- New `punkboot::utils::vfs_boot_library_report` (0.2.0 -> 0.3.0) is the predicate behind + the gate, with its own test suite over directory fixtures. + ## [0.26.0] - 2026-07-26 - punk::zip can READ a zip archive using only stock Tcl - no zipfs, no vfs::zip, no tcllib diff --git a/GOALS-archive.md b/GOALS-archive.md index 6ca00033..5c0ee05a 100644 --- a/GOALS-archive.md +++ b/GOALS-archive.md @@ -202,3 +202,7 @@ Acceptance: an msys/cygwin-runtime tclsh driving bakelist/bake on windows report ### G-124 [achieved 2026-07-26] Dependency-free zip reading in punk::zip: archives, runtime-prefixed executables, and a scripter-facing introspection surface → detail: goals/archive/G-124-punkzip-reader.md Scope: src/modules/punk/zip-999999.0a1.0.tm (reader + introspection procs, base-offset derivation factored out of extract_preamble for shared use, buildversion); src/make.tcl (zipfs-less kit extraction path as first consumer - the zipfile::decode requirement is removed, not made optional); src/tests/modules/punk/zip/ (new testsuite - mkzip round-trip, prefixed-archive cases, introspection, unsupported-input behaviour); bootsupport + src/vfs/_vfscommon.vfs copies via established sync channels Acceptance: punk::zip extracts to a target directory from (a) a plain zip, (b) an executable-prefixed archive with archive-relative offsets and (c) one with file-relative offsets - bin/runtime/win32-x86_64/tclsh90b4_piperepl.exe is the recorded (c) case, on which the current split-then-tcllib path fails with "Bad zip file. Bad closure." - producing CRC-verified byte-identical members with directory entries materialized as directories, on a tclsh with no zipfs, no vfs::zip and no tcllib reachable (the interpreter used is recorded here); an introspection call lists members without extracting, returning per-entry name, size, compressed size, method, mtime, crc and directory-vs-file, over the same three input shapes, and both surfaces carry punk::args definitions with worked examples so 'i punk::zip::' documents them; unsupported inputs (zip64, encrypted, unknown compression method) fail naming the reason rather than emitting partial or garbled output; make.tcl's zipfs-less kit extraction consumes the new surface and NO zipfile::decode requirement remains anywhere in the shipped tree, verified by a tree grep, and demonstrated by an 8.6 zipfs-less bake of a zip kit on a tcllib-less tclsh (msys2 /usr/bin/tclsh8.6 as reference host) producing an artifact that boots with its tcl_library present; a new src/tests/modules/punk/zip/ suite covers a mkzip -> read round trip asserting names, content, directory-vs-file classification and stored attributes - recording the answer to the standing zipfs dir-misidentification question noted in punk::mix::cli and src/vfs/mkzipfix.vfs - plus the three prefixed-archive cases, the introspection output, and the unsupported-input failures; punk::zip buildversion minor-bumped with its changelog line. + +### G-125 [achieved 2026-07-27] A kit that cannot boot is not deployed: extraction failure gates the bake instead of warning past it → detail: goals/archive/G-125-unbootable-kit-deploy-gate.md +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) +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. diff --git a/GOALS.md b/GOALS.md index 9dae7b04..7e7222d5 100644 --- a/GOALS.md +++ b/GOALS.md @@ -390,9 +390,6 @@ Scope: src/scriptapps/punk-runtime.ps1 + src/scriptapps/bin/punk-runtime.bash + Detail: goals/G-123-thirdparty-runtime-tiers.md -### G-125 [proposed] A kit that cannot boot is not deployed: extraction failure gates the bake instead of warning past it -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) -Detail: goals/G-125-unbootable-kit-deploy-gate.md ### G-126 [proposed] punkzip as a vendored zip accelerator: zig 0.16 port, reproducible build into bin/, punk::zip fast path Scope: src/tools/punkzip/ (vendored punkzip source tree - root name settled in the work - with provenance and licence records); src/make.tcl (tool build step); bin/tools/zig* (pinned toolchain as consumed); bin/punkzip.exe (untracked build output); src/modules/punk/zip-999999.0a1.0.tm (accelerator detection + fast path behind the G-124 pure-Tcl floor); src/tests/modules/punk/zip/ (parity suite - accelerated and pure-Tcl paths must agree); upstream maintained checkout c:/repo/jn/zig/punkzip (re-vendor source, read-only from this repo) diff --git a/goals/G-028-file-locker-identification.md b/goals/G-028-file-locker-identification.md index ef767a5f..1cde3352 100644 --- a/goals/G-028-file-locker-identification.md +++ b/goals/G-028-file-locker-identification.md @@ -59,6 +59,11 @@ the kind of file this fails on. ## Notes +- Related: G-125 (achieved 2026-07-27 - see goals/archive/G-125-unbootable-kit-deploy-gate.md) - + the sibling refusal in the same deploy step, and a precedent for its shape: a kit that + fails the boot-precondition gate is reported under FAILED KITS with a reason naming the + cause and nothing is written. This goal names the PROCESS holding a target the build + cannot replace; both are "why bin/ was not updated" answers and should read alike. - Related: G-023 (versioned binary deploys are the recurring customer), G-020 (same optionally-available twapi/cffi pattern for platform features), punkboot::utils chicken-and-egg ordering rules in src/bootsupport/AGENTS.md diff --git a/goals/G-101-tcl86-kit-container-strategy.md b/goals/G-101-tcl86-kit-container-strategy.md index ab70bd62..3ad30f58 100644 --- a/goals/G-101-tcl86-kit-container-strategy.md +++ b/goals/G-101-tcl86-kit-container-strategy.md @@ -45,6 +45,14 @@ unblocked; activation is the user's call. ## Notes +- Related: G-125 (achieved 2026-07-27 - see goals/archive/G-125-unbootable-kit-deploy-gate.md) - + a new container type is exactly where an extraction path can silently produce nothing, + and the bake now refuses such a kit instead of deploying it. Concretely for this goal: + whichever container is chosen must land a tcl library in the merged vfs at + `tcl_library/` or `lib/tcl./` (with a companion file beside init.tcl), or + the gate's location list in `punkboot::utils::vfs_boot_library_report` needs extending + to recognise the new shape - the check is the place where "this container produced a + bootable kit" is asserted. - Related: G-124 (achieved 2026-07-26 - see goals/archive/G-124-punkzip-reader.md) - an 8.6 container has no zipfs by definition, so whichever container this goal picks needs a reader that works without it. That reader now exists and is verified on 8.6 with no diff --git a/goals/G-125-unbootable-kit-deploy-gate.md b/goals/G-125-unbootable-kit-deploy-gate.md deleted file mode 100644 index 1cd63d0e..00000000 --- a/goals/G-125-unbootable-kit-deploy-gate.md +++ /dev/null @@ -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/` nor `bin/` 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. diff --git a/goals/G-127-crosstarget-vfs-bake.md b/goals/G-127-crosstarget-vfs-bake.md index 13c5c454..0370ef1b 100644 --- a/goals/G-127-crosstarget-vfs-bake.md +++ b/goals/G-127-crosstarget-vfs-bake.md @@ -89,6 +89,16 @@ exists locally (empty but for sha1sums.txt) and punkbin upstream carries - Depends on: G-122 (achieved) - the input half; this is the output half of the same split, and retires the TODO that goal left standing. +- Related: G-125 (achieved 2026-07-27 - see goals/archive/G-125-unbootable-kit-deploy-gate.md; + pointer pushed at its archive sweep, where the pair were each other's only recorded + bridge). It added a boot-precondition gate that refuses a kit BEFORE any build product + is written - immediately after the vfs merges, before the kit-type assembly switch. Two + consequences for this goal: whatever output location it settles on for non-native kits + (bin/kits//) inherits the never-written guarantee for free as long as the gate + stays upstream of the artifact write, and the payload/target mismatch reporting this + goal adds should read as one vocabulary with the gate's refusal rather than a second, + differently-worded failure surface. The gate is deliberately non-executing precisely so + it covers cross-target kits this host could never run. - Related: G-115 - owns the payload DECLARATION FORMAT and is currently written without any platform axis (zero mentions of platform or target). Whichever of the two lands second adopts the other: this goal supplies the axis, that goal supplies the format. diff --git a/goals/archive/G-125-unbootable-kit-deploy-gate.md b/goals/archive/G-125-unbootable-kit-deploy-gate.md new file mode 100644 index 00000000..7a1decbe --- /dev/null +++ b/goals/archive/G-125-unbootable-kit-deploy-gate.md @@ -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/` nor `bin/` 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// 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` + `.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/` and `bin/` are never touched and the punkcheck + records stay consistent with what is on disk. Note the merged `src/_build/.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./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/ supplies one" (which described a consequence the build then ignored) to + "will FAIL the boot-precondition gate unless src/vfs/ 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. diff --git a/punkproject.toml b/punkproject.toml index 31051322..80a3138e 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,4 +1,4 @@ [project] name = "punkshell" -version = "0.26.0" +version = "0.27.0" license = "BSD-2-Clause" diff --git a/src/AGENTS.md b/src/AGENTS.md index a3e5f178..85f79d1d 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -71,6 +71,7 @@ Recovery after a wrong path guess: - `tclsh src/make.tcl workflow` prints an embedded ASCII data-flow overview of the build/release workflow (release-ready command sequence, source→outputs folder flow, kit assembly, keyed notes). The text lives in `::punkboot::workflow_text` in `src/make.tcl` - embedded rather than a data file so it travels with the make.tcl copies seeded into generated projects. **Update contract (agents):** whenever build data flow changes - a make.tcl subcommand added/removed/repurposed, a source or output folder added or rerouted, a propagation target added, a gate (staleness/provenance/confirm) or deploy behaviour changed - update the workflow text in the same change-set. Keep it plain ASCII, max line width 100, and preserve the RELEASE SEQUENCE / DIAGRAM / KEY-NOTES / MAINTENANCE structure; verify by running `tclsh src/make.tcl workflow` (and once under `PUNKBOOT_PLAIN=1`). Adding/changing a subcommand also means: SUMMARIES + HELPTEXTS + SUBOPTS (+ SUBGROUPS) in `::punkboot::argdoc`, `known_commands`, the plain `punkboot_gethelp` text, and a project-version bump (make.tcl interface is product surface per root AGENTS.md). Layout make.tcl copies pick the change up via `make.tcl bootsupport` - do not hand-sync them. - make.tcl subcommands and flags are punk::args-declared (G-030): `tclsh src/make.tcl help ?subcommand?` or ` -help` shows tabled usage; invalid arguments produce punk::args usage errors. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting. The `::punkboot::argdoc` definitions are constructed (string-built) and use the G-045 authoring mechanisms — block-form help values + `@normalize`, explicit `@form -synopsis` — see src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions. - Uncommitted `make.tcl`-generated outputs may be batched into one commit, regardless of how many `make.tcl` invocations produced them. This covers punkcheck-managed build outputs that are VCS-tracked: `src/bootsupport/`, `_vfscommon.vfs/modules` + `/lib`, declared per-kit `*.vfs/lib_tcl/` subfolders, the thin-layout sync copies (`src/project_layouts/vendor/punk//src/{make.tcl,build.tcl}` + bootsupport manifest copies), and the module-shipped layout payload under `src/modules/punk/mix/#modpod-templates-*/templates/project_layouts/` (G-087: synced from `src/project_layouts` - never hand-edit the modpod copy). (Root `modules/`, `lib/`, `modules_tcl/`, `lib_tcl/` are gitignored and never committed.) Each module's old→new version rename is atomic; a regeneration reflects one build-output refresh, not independent hand-edits. This is a carve-out from generic "split aggressively" commit guidance for punkcheck-managed build outputs only — it does not cover user-curated VFS content (`_config/`, per-kit `*.vfs/` outside declared `lib_tcl` subfolders, `_aside/`, `mkzipfix.vfs`, `_vfscommon.vfs/doc`), which commits separately per its own concerns. +- **Boot-precondition gate (G-125).** A bake 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/`, no deploy, and the previously deployed `bin/` is left byte-identical. This is deliberately a failure rather than a warning: the deploy step deletes the old kit before copying the new one, so the pre-gate behaviour replaced a working shell with an artifact that died at startup with `application-specific initialization failed: Cannot find a usable init.tcl`. The check is structural and non-executing (so it covers cross-target kits too): `tcl_library/init.tcl` or `lib/tcl./init.tcl`, plus one companion file a real Tcl library carries beside it (`tm.tcl`, `package.tcl`, `auto.tcl`, `clock.tcl`, `history.tcl`, `word.tcl`) or an `encoding/` directory - without that second test the `lib/BWidget1.10.1/init.tcl` every punkshell kit carries would answer for a tcl library it cannot provide. It reads the MERGED tree rather than asking whether extraction ran, so a `.vfs` that legitimately supplies its own tcl library (`src/vfs/punk8_statictwapi.vfs`, `src/vfs/punk9test.vfs`) builds and deploys unchanged. **Remedy when a kit is refused:** the usual cause is that the runtime's own payload could not be extracted - read the recapped `BUILD-WARNING` naming what was tried, then either fix extraction (a driving tclsh with no zipfs needs punk::zip >= 0.2.0 in bootsupport - see the extraction bullet above), point the kit at a runtime that carries a payload, or have the kit's `.vfs` supply a tcl library itself. The predicate is `punkboot::utils::vfs_boot_library_report` called through a guarded require, exactly as the dirty-src provenance check is: a stale or missing bootsupport snapshot degrades the gate to a `NOTE` rather than failing every kit. `tclsh src/make.tcl check` reports whether the gate is `ACTIVE` or `UNAVAILABLE`. - Kit bakes consume the punk-runtime WORKING COPIES under `bin/runtime//`: `make.tcl bake`/`bakehouse` emit a `BUILD-WARNING:` (recapped at end of run) when a wrapped runtime's beside-toml revision is older than an `-r` artifact present in the same folder - the forgot-to-switch-back guard for deliberate `punk-runtime use ` testing excursions. Heed it before trusting freshly baked family kits; `bin/punk-runtime.cmd use >` (`.exe` suffix optional) re-materializes the working copy. - `tclsh src/make.tcl bakelist ?kitname ...?` (G-121) reports the kit outputs configured in `src/runtime/mapvfs.config`: kit name, kit type, runtime (with presence in the runtime store), vfs folder, and the deployed state of `bin/` vs the `src/_build` build product (`current|stale|absent|nobuild`), with anomalies (`runtime=missing`, `vfs=missing`, `rtrev=r` materialization staleness) and the cross-target marker `target=` in a trailing notes column. A nonexistent store tier is flagged loudly per tier actually referenced (header `(FOLDER MISSING)` for the default tier + stderr derivation; bake's no-runtimes exit self-diagnoses the same way). Name arguments filter the report and add a per-kit detail block (resolved store tier, target and provenance of the target, paths/sizes/mtimes). `make.tcl bake ?kitname ...?` bakes and deploys only the named kits - other kits' `_build`/`bin` artifacts and punkcheck records are untouched and the vfslibs phase narrows to the named kits' vfs folders; an unknown name errors before any build, listing the configured names; flags go before kit names (`make.tcl bake -confirm 0 punk91`). Bare `bake` processes all configured kits as before. Both surfaces consume the shared parsed-mapping helpers (`::punkboot::lib::mapvfs_*`) rather than the file format, so the G-024 config-format work swaps the reader underneath. Piped characterization: `src/tests/shell/testsuites/punkexe/maketclbakelist.test`. - **Host vs target platform (G-122).** Everything a bake EMITS is keyed by the artifact's TARGET platform, never by the driving tclsh's personality: the `bin/runtime/` store the runtime is read from, `.exe` suffixing of runtime files and kit outputs, the presence checks behind `runtime=missing`, and whether the pre-deploy process sweep uses `tasklist`/`taskkill` or `ps`/`kill`. Host semantics - copy commands, path handling, filesystem case rules, prompts - stay keyed to the host. The default target is the host's own platform canon, EXCEPT a cygwin-family host (an msys2/cygwin-runtime tclsh, which reports `tcl_platform(platform)` `unix` on windows and canonizes as `msys-x86_64`/`cygwin-x86_64`), which targets `win32-x86_64`: such a host now drives the identical kit set, names and store addressing as a native tclsh. `tclsh src/make.tcl check` prints the derivation on one `platform (G-122): host=... target=... store=... exe-suffix=... process-tooling=...` line. A mapvfs entry may declare its own target platform (4th element - see `src/runtime/AGENTS.md`), which is then read from that platform's tier, named with that platform's executable convention, and skipped by the process sweep (its processes are not visible to this host). Traps this closes, all field-verified 2026-07-26: msys `ps` cannot see a natively-launched kit (only `tasklist` can), and msys2 rewrites arguments that look like absolute posix paths when spawning a native windows program - so `taskkill /PID ` arrived as `taskkill C://PID `; native-windows command lines therefore go through `::punkboot::exec_nativeargs` (sets `MSYS2_ARG_CONV_EXCL=*`, inert elsewhere). Also note a cygwin-family tclsh is a POSIX Tcl: name the script the way that shell spells paths (`tclsh /c/repo/.../src/make.tcl`, not `C:/repo/...`, which it resolves relative to the cwd) - make.tcl diagnoses the windows-spelling case. Piped characterization: `src/tests/shell/testsuites/punkexe/maketclplatform.test` (the cygwin-host tests self-gate on finding and probing a real msys/cygwin tclsh; `PUNK_MSYS_TCLSH` names one explicitly). diff --git a/src/make.tcl b/src/make.tcl index 68653cf4..4c119425 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -2281,6 +2281,12 @@ DIAGRAM 2 - KIT ASSEMBLY DETAIL (the 'make.tcl bake' stage; incl. vfslibs phase) | copy common, then merge overlay over it v src/_build/.exe.vfs + | + | boot-precondition gate: merged vfs must supply a tcl + | library (tcl_library/ or lib/tcl./) [K10] + v + (gate) --> refused: kit listed under FAILED KITS, nothing + | built, nothing deployed, bin/ untouched | | zip image appended to the runtime exe: tcl::zipfs::mkimg, | or (driving tcl without zipfs, e.g 8.6) raw-runtime split @@ -2354,6 +2360,21 @@ KEY / NOTES 'make.tcl check' prints the derivation; 'make.tcl bakelist' shows per-kit targets, with a target= note on rows that are not on the default target. +[K10] BOOT-PRECONDITION gate (G-125). A kit whose merged vfs has no tcl library + cannot initialise at all ('Cannot find a usable init.tcl'), so the bake + refuses it: the kit is listed under FAILED KITS and NOTHING is written - + no src/_build/, no deploy, and the previously deployed bin/ is + left exactly as it was. This is deliberately not a warning: the deploy step + deletes the old kit before copying the new one, so warning-and-proceeding + replaced a working shell with one that could not start. The check is + structural (tcl_library/init.tcl or lib/tcl./init.tcl, plus a + companion file so a package's own init.tcl cannot answer for a tcl library) + and runs on the MERGED tree, not on whether extraction ran - a .vfs that + supplies its own tcl library builds and deploys as normal. Nothing is + executed, so cross-target kits are covered too. Usual cause: the runtime's + own payload could not be extracted - see the BUILD-WARNING naming what was + tried. 'make.tcl check' reports whether the gate is ACTIVE. + MAINTENANCE (agents take note) ---------------------------------------------------------------------------- This text is embedded in make.tcl (::punkboot::workflow_text). When the build @@ -3264,6 +3285,21 @@ proc ::punkboot::get_src_provenance_warnings {projectroot label} { set checked_vcs_roots [dict create] return [list 1 [::punkboot::utils::vcs_dirty_warnings $projectroot checked_vcs_roots $label src]] } +#Availability probe + boot-precondition check of an assembled kit vfs (G-125), shared by the +#kit gate in the bake loop and the 'check' command report. Same guarded-require treatment as +#the provenance check above: a stale or missing punkboot::utils snapshot must not brick the +#commands used to repair it, so an unavailable check degrades to a notice rather than failing +#every kit. Returns {available report}. +proc ::punkboot::get_vfs_boot_library_report {vfsfolder} { + set available [expr {\ + ![catch {package require punkboot::utils}]\ + && [llength [info commands ::punkboot::utils::vfs_boot_library_report]]\ + }] + if {!$available} { + return [list 0 [dict create]] + } + return [list 1 [::punkboot::utils::vfs_boot_library_report $vfsfolder]] +} #Wrap ::exit so accumulated provenance warnings are recapped at the very end of output no matter #which of make.tcl's many exit points a command takes. if {![llength [info commands ::punkboot::exit_original]]} { @@ -3506,6 +3542,19 @@ if {$::punkboot::command eq "check"} { puts stdout " build/promotion commands (project packages modules libs vfs vfslibs bin bootsupport vfscommonupdate) will WARN as above and proceed." puts stdout " With -dirty-abort they would abort. To evaluate uncommitted source without building, use ' src' or ' src shell'." } + # Kit boot-precondition gate status - whether a bake can refuse an unbootable kit + puts stdout $sep + #availability probe only - the path deliberately does not exist + lassign [::punkboot::get_vfs_boot_library_report [file join $projectroot __punkboot_bootgate_probe__]] _boot_available _boot_report + if {$_boot_available} { + puts stdout "boot-precondition gate (G-125): ACTIVE" + puts stdout " bake checks each kit's merged vfs for a tcl library (tcl_library/ or lib/tcl./) before" + puts stdout " building it. A kit with none is reported under FAILED KITS and is neither built nor deployed, so a" + puts stdout " previously deployed kit is never replaced by an artifact that cannot initialise." + } else { + puts stdout "boot-precondition gate (G-125): UNAVAILABLE (punkboot::utils vfs_boot_library_report not loadable from bootsupport)" + puts stdout " bake will proceed with a NOTE and cannot refuse an unbootable kit - run 'make.tcl modules' then 'make.tcl bootsupport'." + } puts stdout $sep exit 0 } @@ -6047,13 +6096,15 @@ foreach vfstail $vfs_tails { } } ;#end while !$extraction_done && [llength $extraction_trylist] + set extraction_tried "$target_kit_type[expr {[llength $source_kit_caps] ? " + runtime caps $source_kit_caps" : ""}]" if {!$extraction_done} { #TODO: if not extracted - use a default tcl_library for patchlevel and platform? - #Recapped BUILD-WARNING (not just an inline note): the build continues - #and produces an artifact, but unless the .vfs itself supplies a - #tcl_library that artifact cannot boot - and this line would otherwise - #scroll away behind thousands of merge lines. - ::punkboot::print_build_warnings [list "No extraction done from runtime $runtime_fullname (tried: $target_kit_type[expr {[llength $source_kit_caps] ? " + runtime caps $source_kit_caps" : ""}]) - kit $targetkit gets no tcl_library from the runtime and will not initialise unless src/vfs/$vfstail supplies one"] + #Recapped BUILD-WARNING (not just an inline note): this line would + #otherwise scroll away behind thousands of merge lines. It is a warning + #rather than the failure itself because a .vfs that supplies its own tcl + #library needs no extraction - the G-125 gate below decides, on the + #merged tree, whether this kit can initialise. + ::punkboot::print_build_warnings [list "No extraction done from runtime $runtime_fullname (tried: $extraction_tried) - kit $targetkit gets no tcl library from the runtime, so it will FAIL the boot-precondition gate unless src/vfs/$vfstail supplies one"] file mkdir $targetvfs } @@ -6111,6 +6162,36 @@ foreach vfstail $vfs_tails { set sourcevfs [file join $sourcefolder vfs $vfstail] merge_over $sourcevfs $targetvfs + #G-125 boot-precondition gate. A kit with no tcl library cannot initialise + #at all - 'Cannot find a usable init.tcl' - so refuse it HERE: before any + #build product is written, and long before the deploy step deletes the + #previously working bin/ and copies the new one over it. The check is + #on the MERGED tree rather than on whether extraction ran, because a .vfs + #that supplies its own tcl library is legitimate and must keep building + #(src/vfs/punk8_statictwapi.vfs and punk9test.vfs are such folders today). + #Structural and non-executing, so it also covers cross-target kits, which + #this host could never run. + lassign [::punkboot::get_vfs_boot_library_report $targetvfs] bootcheck_available bootcheck + if {!$bootcheck_available} { + puts stderr "NOTE: kit boot-precondition check unavailable (punkboot::utils vfs_boot_library_report not loadable from bootsupport) - continuing without it" + } elseif {![dict get $bootcheck ok]} { + if {$extraction_done} { + #extract_kit_type is only recorded by some of the extraction branches + set extractedas [expr {$extract_kit_type ne "" ? " (as $extract_kit_type)" : ""}] + set gatecause "extraction from runtime $runtime_fullname$extractedas ran but supplied no tcl library, and src/vfs/$vfstail does not supply one either" + } else { + set gatecause "nothing was extracted from runtime $runtime_fullname (tried: $extraction_tried) and src/vfs/$vfstail does not supply a tcl library" + } + set failmsg "kit cannot initialise - $gatecause. Checked [join [dict get $bootcheck checked] { and }] in the merged vfs; [dict get $bootcheck reason]. NOT BUILT and NOT DEPLOYED: any previously deployed $targetkit is untouched" + puts stderr "boot-precondition gate FAILED for kit $targetkit - refusing to build an artifact that cannot initialise" + puts stderr " $gatecause" + lappend failed_kits [list kit $targetkit reason $failmsg] + $vfs_event targetset_end FAILED + $vfs_event destroy + $vfs_installer destroy + continue + } + set wrapvfs $targetvfs switch -- $target_kit_type { zip { diff --git a/src/modules/punkboot/utils-999999.0a1.0.tm b/src/modules/punkboot/utils-999999.0a1.0.tm index 1fdbb2a3..89d3e96f 100644 --- a/src/modules/punkboot/utils-999999.0a1.0.tm +++ b/src/modules/punkboot/utils-999999.0a1.0.tm @@ -23,7 +23,7 @@ package require Tcl 8.6- # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ namespace eval punkboot::utils { variable version 0.1.0 - namespace export parse_punkproject_version read_punkproject_version read_changelog_latest_version vcs_dirty_warnings + namespace export parse_punkproject_version read_punkproject_version read_changelog_latest_version vcs_dirty_warnings vfs_boot_library_report namespace eval argdoc { @@ -227,6 +227,103 @@ namespace eval punkboot::utils { } return $warnings } + + + #Companion files of a real Tcl library directory. init.tcl sources these, so a + #directory holding init.tcl with none of them beside it is a package's own init + #script (lib/BWidget1.10.1/init.tcl and friends), not a tcl library. + variable boot_library_companions {tm.tcl package.tcl auto.tcl clock.tcl history.tcl word.tcl} + + namespace eval argdoc { + lappend PUNKARGS [list { + @id -id ::punkboot::utils::vfs_boot_library_report + @cmd -name "::punkboot::utils::vfs_boot_library_report"\ + -summary\ + "Report whether an assembled vfs tree can supply a bootable tcl library"\ + -help\ + "Structural check of a merged .vfs tree for the precondition a kit + needs to initialise at all: a Tcl library the runtime can find. Reads + the directory only - nothing is executed, decompressed or launched, so + a caller such as make.tcl can run it for every kit on every bake and + for a cross-target kit it could never run itself. + + Two conventions are recognised, both observed in this project's kits: + tcl_library/ zipfs-attached kits (tclZipfs looks for + /tcl_library/init.tcl) + lib/tcl./ starkit/tclkit-style kits (lib/tcl8.6, + lib/tcl9.0) + + A candidate directory qualifies only when it holds init.tcl AND at + least one companion file a real Tcl library carries beside it + (tm.tcl, package.tcl, auto.tcl, clock.tcl, history.tcl, word.tcl) or + an encoding/ subdirectory. Without that second test a package's own + init script - lib/BWidget1.10.1/init.tcl, present in every punkshell + kit - would answer for a tcl library it cannot provide. + + Returned dict keys: + ok 1 when at least one qualifying library was found + locations qualifying directories, relative to vfsfolder + rejected candidate directories that held init.tcl but no + companion, relative to vfsfolder - the near-misses + worth naming when a gate fires unexpectedly + checked the location patterns examined, relative to vfsfolder + reason why ok is 0; empty string when ok is 1" + @leaders + vfsfolder -type string -optional 0 -help\ + "Path of the assembled vfs tree to inspect" + }] + } + proc vfs_boot_library_report {vfsfolder} { + variable boot_library_companions + set checked [list tcl_library lib/tcl.] + set report [dict create ok 0 locations {} rejected {} checked $checked reason ""] + if {![file isdirectory $vfsfolder]} { + dict set report reason "no such directory: $vfsfolder" + return $report + } + #candidates are the two conventions only - a full tree walk would find every + #package's init.tcl and cost more than the check is worth + set candidates [list [file join $vfsfolder tcl_library]] + foreach dir [lsort [glob -nocomplain -type d -directory [file join $vfsfolder lib] -- {tcl[0-9]*}]] { + lappend candidates $dir + } + set locations [list] + set rejected [list] + foreach dir $candidates { + if {![file isfile [file join $dir init.tcl]]} { + continue + } + set qualifies 0 + foreach companion $boot_library_companions { + if {[file isfile [file join $dir $companion]]} { + set qualifies 1 + break + } + } + if {!$qualifies && [file isdirectory [file join $dir encoding]]} { + set qualifies 1 + } + set rel [string range $dir [expr {[string length $vfsfolder] + 1}] end] + if {$qualifies} { + lappend locations $rel + } else { + lappend rejected $rel + } + } + if {[llength $locations]} { + dict set report ok 1 + dict set report locations $locations + dict set report rejected $rejected + return $report + } + dict set report rejected $rejected + if {[llength $rejected]} { + dict set report reason "no tcl library in [file tail $vfsfolder]: [join $rejected {, }] hold init.tcl but none of the companion files a tcl library carries beside it ([join $boot_library_companions {, }]) nor an encoding/ directory" + } else { + dict set report reason "no tcl library in [file tail $vfsfolder]: neither tcl_library/init.tcl nor lib/tcl./init.tcl is present" + } + return $report + } } namespace eval ::punk::args::register { diff --git a/src/modules/punkboot/utils-buildversion.txt b/src/modules/punkboot/utils-buildversion.txt index 59fc8f57..0b588fdb 100644 --- a/src/modules/punkboot/utils-buildversion.txt +++ b/src/modules/punkboot/utils-buildversion.txt @@ -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 diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index c7b24eac..ca168964 100644 --- a/src/tests/modules/AGENTS.md +++ b/src/tests/modules/AGENTS.md @@ -38,6 +38,7 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/` ## Child DOX Index - `opunk/console/` — ::opunk::Console backend subclass tests (`testsuites/console/backends.test`, G-001): virtual dispatch of subclass overrides through base-class calls and punk::console::console_spec_resolve (both unchanged), TestConsole determinism + probe-free at_eof, SshConsole capability/eof + the flagship size-via-ANSI-query-over-socket case (a scripted remote terminal answers CSI 6n), TkConsole widget size/eof (gated behind env PUNK_TEST_TK=1 - Tk in the shared testinterp has side effects; also verifiable standalone under a tk-capable kit e.g `punk91 src