20 KiB
G-031 Componentized kit boot: thin project-owned main + shared layout-owned boot core
Status: achieved 2026-08-02 Scope: src/vfs/_config/ (punk_main.tcl, project_main.tcl restructure); src/vfs/_vfscommon.vfs (boot core delivery); src/project_layouts/ (thin-main skeleton + README guidance, via established sync channels); src/vfs/README.md (boot-entry guidance); src/modules/punk/args/moduledoc/ (boot-core API moduledoc); src/make.tcl (startup-script collision gate) Goal: the per-project vfs main script becomes a thin project-owned file - declare the application's subcommands and launch defaults at clearly commented customization points, then hand over to a shared layout-owned boot core (vfs mounts, package modes and paths, libunknown, src-mode modpod registration) and default dispatch pulled in from within the kit - so project developers add app-specific subcommands without wading through or forking ~1000 lines of boot boilerplate, and boot improvements reach derived projects as pull-updatable payload instead of dying in vintage forks (tomlish_main.tcl: ~20 custom lines carrying a stale 500-line 2025 copy of the rest). The boot surface an application developer writes against is documented at the point of use (commented thin main, src/vfs README boot-entry guidance) and runtime-queryable from any kit carrying the boot core. Acceptance: punkshell's own kits boot through a thin main plus shared boot core with behaviour parity - package modes including src mode, existing tclsh/shellspy/punk/shell/script dispatch semantics, and supported vfs types (zipfs/metakit/cookfs) all unchanged; a project-specific subcommand is added by editing only the thin main at a commented customization point (proven end-to-end in a derived project - tomlish replacing its forked main is the natural first); the boot core ships as layout-owned payload (via _vfscommon/layout channels) and the thin main as a project-owned skeleton, per the G-027 ownership classification; the boot core is versioned/identifiable so a kit can report which boot-core vintage it carries (ties to G-025 stamping); developer guidance ships with the split - project_main.tcl is reborn as the heavily commented thin-main sample (its monolith vintage retired) and src/vfs/README.md (repo and project-0.1 layout copies, via the established sync channels) gains a boot-entry section covering the recommended _config/_main.tcl-behind-the-fauxlink path, the supported plain root main.tcl alternative, the inertness of unreferenced _config files, and the one-startup-script rule; the boot-core API is runtime-queryable - a punk::args::moduledoc package (sibling of moduledoc::punkexe) documenting the thin-main contract and the post-boot ::punkboot surface ships via _vfscommon, so a kit answers boot-API queries the same way it answers launch-surface queries today; bake refuses to build a kit whose source .vfs carries both a root main.tcl and a root fauxlink resolving to main.tcl (per-kit refusal naming both files, other kits proceed), replacing today's silent traversal-order-dependent outcome, with the check unit-covered.
Context
src/vfs/_config/punk_main.tcl is a 1190-line single apply block mixing two
very different concerns:
- Boot machinery (~1060 lines): vfs mount detection (zipfs/metakit/cookfs), starkit handling, package-mode token parsing (dev/os/src/internal), module and auto_path setup, libunknown, src-mode #modpod registration.
- Application dispatch (~100 lines at the tail): subcommand extraction and the tclsh/shellspy/punk/shell/script cases.
Every kit's main.tcl is already a fauxlink to one such file per project
(main.tcl#..+_config+<name>_main.tcl#@punk::boot,merge_over), and derived
projects fork the whole file to customize the tail. Measured cost (2026-07-07):
- tomlish_main.tcl: 547 lines, of which the genuinely tomlish-specific dispatch tail is ~20 lines - the rest is a 2025-vintage copy of the boot boilerplate, already 632 diff-lines adrift from current punk_main in the first 450 lines. Boot improvements since (src mode, modpod registration) never reached it.
- project_main.tcl (layout template): 901 lines, same story at template vintage.
A project developer wanting one custom subcommand must wade through - and then permanently own - a thousand lines of boot code.
Approach
- Split into:
- Boot core (layout-owned): the boot machinery plus the default dispatch,
shipped inside the kit via _vfscommon.vfs (every kit already merges it) and
sourced from the thin main by vfs-relative path. Layout-owned per the G-027 ownership classification -> pull-updatable in derived projects, so boot fixes propagate instead of dying in forks. - Thin project main (project-owned skeleton): declares project subcommands and launch defaults at clearly commented customization points, then hands over to the boot core. This is the file a project developer reads and edits.
- Boot core (layout-owned): the boot machinery plus the default dispatch,
shipped inside the kit via _vfscommon.vfs (every kit already merges it) and
- Boot-phase constraint: the boot core cannot be a package (no package paths exist yet) - it is sourced by vfs-relative path from the thin main. The default dispatch portion runs post-boot and may be package-based if that proves cleaner; record the split point here when decided. DECIDED 2026-08-02 (increment 2): the default dispatch stays IN the core file - one sourced file, no package hop; the dispatch handlers are already one-line package requires, so a package-based dispatch would add a moving part for no reduction. The core is a naked script body (no apply wrapper of its own) sourced from inside the thin main's one-level 'apply {args {...}}' frame - the dispatch's 'uplevel 1' semantics (evaluate at global level) are preserved by construction and the contract is stated in the core header.
- Registration model: the thin main declares subcommands (name -> handler package/command, plus optional punk::args definition id for G-032) before invoking the default dispatch; built-ins (tclsh/shellspy/punk/shell/script, and G-025's version-report) come from the boot core, project additions merge in. Default-subcommand and no-args behaviour are declared, not hardcoded (a shell-like default remains the punkshell behaviour; a tool-like default such as tomlish's -help injection becomes a declared choice).
- Documentation delivery: the thin main IS the primary sample - project_main.tcl reborn with heavily commented customization points, retiring its 901-line monolith vintage. src/vfs/README.md (repo + project-0.1 layout copy) carries the boot-entry guidance for humans: recommended path (edit _config/_main.tcl behind the fauxlink - shared by multiple kits, e.g tomlish's two shell kits), supported plain root main.tcl alternative (tomlish sample.vfs is a live exhibit), one-startup-script rule, and the _config-inertness misconception (files there reach kits only as fauxlink targets). The boot-core API is documented runtime-queryably as a punk::args::moduledoc package delivered like moduledoc::punkexe (mint + promote to _vfscommon): thin-main registration contract plus the post-boot ::punkboot procs - live definitions where procs persist post-boot, display-first otherwise.
- Boot core carries its own version/identity so a kit can report the boot vintage it embeds (G-025 stamping is the natural vehicle).
- Layout skeletons follow through the established sync channels (custom/ _project sync step now; G-012 (achieved 2026-08-01 - see goals/archive/G-012-template-payload-safety.md)/G-027 for the rest) - noted in completion reports, never hand-synced.
Alternatives considered
- Leaving the monolith and documenting "edit the tail" - rejected: the fork cost is proven (tomlish, project_main both adrift); documentation does not stop vintage divergence, pull-updatable payload does.
- Making the entire main (incl. dispatch tail) layout-owned and pushing project customization into data/config only - rejected for now: subcommand handlers are code; a thin project-owned code file with a clear contract is simpler than inventing a config schema for arbitrary launch behaviour. Revisit if the thin mains themselves start accreting boilerplate.
- Collision as BAKE-WARNING (bake proceeds) - rejected: both-present has no correct winner; unlike a MISSING startup script (legal bare kit, warn-only), ambiguity is a kit-definition error. Revisit only if a legitimate both-present use case appears.
Notes
- Sequencing with G-015: resolved - G-015 proceeded first on the monolith (achieved 2026-07-07 as the dedicated app-punkscript package, see goals/archive/G-015-script-subcommand-piped-stdin.md), so its dispatch logic moves into the boot core here and this goal must preserve the script subcommand's behaviour.
- Related: G-027 (ownership + pull channel this is designed for), G-025 (boot-core vintage reporting), G-032 (punk::args-defined dispatch built on the registration model), G-012 (layout payload hygiene - achieved 2026-08-01), G-018 (plain tclsh kits deliberately carry NO punk boot layer - out of scope here).
- G-033 (archived) recorded a fork-drift datapoint for this goal: the proj:
scope prefix and its
::punkboot::proj_root_findwalk-up landed in punk_main.tcl only - project_main.tcl still lacks src mode and now also proj:, widening the divergence this consolidation resolves - see goals/archive/G-033-proj-mode-cwd-project.md. - G-089 relationship (recorded 2026-07-24 after overlap review): its boot-provided packagemode hint (for scriptlib resolution) is a boot-core interface this goal's registration model should carry - one boot "running state" surface, not per-feature side channels.
- G-118 relationship (achieved 2026-07-25 - see goals/archive/G-118-tclsh-subcommand-review.md): its item-9 project_main.tcl parity duplication is deliberately interim (recorded in G-118's decision line); this goal's boot core is the consolidation path. The mirrored tclsh dispatch now lives byte-synced in punk_main.tcl, project_main.tcl and both layout _config copies - the duplication this goal retires.
- The tomlish silent-exe incident (2026-07-07, tomlish 1.1.10 fix) is a reminder that launch-path contracts are easy to break invisibly - behaviour parity in the acceptance means exercising each dispatch case, not just booting.
- 2026-07-25 (user question, recorded as a consumer candidate):
make.tcl shellcurrently passes its arguments raw to the repl's ::argv. Making it accept the punk executable's launch surface (package modes, script/tclsh subcommand forms - the contract documented by punk::args::moduledoc::punkexe since archived G-118) would be most cleanly done by make.tcl SOURCING the shared boot core this goal delivers, rather than re-implementing punk_main dispatch in build tooling - a plain runtime + repo checkout would then present the same surface as a kit (src/dev modes map naturally; kit-payload notions like os/internal follow the boot core's own zipfs-presence branching). Candidate follow-on when this goal activates. - 2026-08-02 guidance/API decisions (user direction): the recommended customization path is editing _config/_main.tcl behind the fauxlink; a plain root main.tcl (no fauxlink) stays a supported simple shape; a project main need not use the boot-core API at all - opting out entirely remains legal (G-018 precedent: plain tclsh kits carry no punk boot layer; bake must not assume the API is in use). Boot-core API documentation must be runtime-queryable, not README-only.
- project_main.tcl status verified 2026-08-02: no kit mapping in src/runtime/mapvfs.toml - never baked in punkshell itself; live only as layout template payload (project-0.1 project.vfs boots through it via fauxlink) and known-adrift (G-033: lacks src mode and proj:; only the tclsh dispatch was byte-synced under G-118 interim). Its rebirth as the thin-main sample is therefore a behaviour change for GENERATED projects only - parity exercise belongs there (project.new E2E per the G-087 workflow).
- Startup-script collision evidence (2026-08-02): merge_over's fauxlink branch materialises with file copy -force and NO overwrite warning (make.tcl ~7780); only the plain-file branch warns (~7799); vfs_startup_script_warning returns clean when a real main.tcl exists, so both-present is undetected and the built kit's boot script depends on traversal order - typically the fauxlink lands second and wins, so the developer's drop-in file silently loses. Gate naturally extends vfs_startup_script_warning (root-level check only; nested app/main.tcl fauxlinks like punk8win's are a different path and exempt; overlay-only folders remain unchecked). Independent of the split - may land ahead of the rest as an early increment.
- Doc-home rationale (2026-08-02): src/vfs/README.md (repo and layout copies) currently documents only G-115 payload declarations; the boot-entry contract exists only in agent-facing src/vfs/AGENTS.md - the README section fills a real human-facing gap. Moduledoc precedent: punk::args::moduledoc::punkexe (G-118) documents the launch surface, minted 0.2.0 + promoted to _vfscommon 2026-08-02. Naming: ::punkboot is already the boot core's persistent runtime namespace (G-129 procs), so punk::args::moduledoc::punkboot is the natural id home; the moduledoc documents the API vintage alongside the G-025 stamp rather than duplicating it.
Progress
- 2026-08-02 activated (same day as the user-approved contract extension: guidance deliverables, runtime-queryable boot-core API moduledoc, startup-script collision gate).
- 2026-08-02 increment 1 LANDED - startup-script collision gate (0.44.0): punkboot::utils::vfs_startup_script_report 0.7.0 censuses root-level suppliers of the name main.tcl by RESOLVED fauxlink name (empty-nominalname links counted, nested app/main.tcl exempt); ok=0 only when suppliers > 1. make.tcl: guarded-require wrapper get_vfs_startup_script_report, per-vfs verdict beside the missing-startup warning, per-kit refusal under FAILED KITS placed after the self-bake guard and before the punkcheck installer - nothing is written for a refused kit; other folders' kits proceed. Fixture suite startupscript.test (12 tests incl a real-source-tree sweep proving current src/vfs folders collision-free); bootlibrary.test re-run green. Minted + promoted (bootsupport, vfscommon); layout make.tcl copies (basic, project-0.1, templates modpod) synced by the modules step. Gate probed live against a bootsupport-only tm path (0.7.0 served, collision fixture ok=0).
- 2026-08-02 increment 2 LANDED - the split itself, punkshell side (0.45.0): boot core extracted verbatim-plus-contract to src/vfs/_config/punkboot_core.tcl (::punkboot::core_version 0.1.0), delivered as /punkboot/core.tcl by a new vfscommonupdate step; punk_main.tcl reborn as the thin main (publishes launch_args/main_script, ==CUSTOMIZE== declaration points, three-candidate core lookup: vfs root, parent dir for nested app/main.tcl, sibling master for source-tree execution). Registration model live: project_subcommands merge in (built-ins unshadowable, reported+ignored on collision), launch_defaults noargs/unknownfirst declared not hardcoded. Parity evidence: full punkexe suite 110/0 against freshly baked punk902z; kitmountpoint 4/4 (8.6-backport exe-path mount, 32-bit modern //zipfs:/app, 64-bit kit, unattributed-mount stderr pin) - the sibling-master candidate exists BECAUSE those tests run punk_main.tcl as a plain script under raw runtimes (a supported shape to preserve); registration model exercised via a scratch fake.vfs under plain tclsh (declared subcommand + tool-style defaults + shadow refusal); 11-kit default bake, only known-baseline warnings; punk86 metakit/starkit 8.6.17 smoke green. cookfs branch carried verbatim - NO cookfs kit is currently mapped in mapvfs.toml to exercise it (stale bin/punk9cook.exe predates the split and shows an old monolith vintage - not a regression).
- 2026-08-02 increment 3 LANDED - guidance deliverable, layout side (0.46.0): project_main.tcl reborn as the heavily commented thin-main sample (1132-line 2025-vintage monolith retired; ==CUSTOMIZE== blocks with worked tool-style examples, opt-out + collision rule documented in-file); project-0.1 layout _config hand-swept (thin punk_main + sample + punkboot_core.tcl master; punkboot/core.tcl seeded in layout _vfscommon.vfs) with the templates modpod mirror following via the modules-step sync; boot-entry sections added to src/vfs/README.md in repo and layout voices; src/vfs/AGENTS.md refreshed (ownership split, collision gate, G-129 note consolidated). E2E on a freshly generated punk.project project: generation completes incl fossil initial commit; boots through its own _config trio via the sibling master; a 'demo' subcommand added by editing ONLY the generated thin main at its customization points dispatches in all three launch forms (explicit, noargs, unknownfirst) - the acceptance's edit-only-the-thin-main property, demonstrated in a generated project via the filesystem-execution shape (kit-bake form of the proof belongs to the tomlish increment).
- 2026-08-02 increment 4 LANDED - runtime-queryable boot-core API doc + vintage reporting (0.47.0, with G-025): punk::args::moduledoc::punkboot 0.1.0 ships via _vfscommon (thin-main contract + registration model as display-first ids (script)::punkboot / ::registration, live definitions for the persistent procs zipfs_mount_pairs / zipfs_kit_mountbase / path_within / proj_root_find) - all six ids resolve in-kit via punk::args::get_ids after update_definitions. Boot core 0.2.0 publishes ::punkboot::boot_state (the G-089-anticipated running-state surface) and the G-025 stamp/report carry boot_core_version - the vintage tie-in acceptance clause is met (a kit reports which boot-core vintage it embeds via 'buildinfo', from embedded data).
- 2026-08-02 increment 5 LANDED - tomlish derived-project proof, ACHIEVED flip: tomlish's 547-line forked tomlish_main.tcl replaced by a thin declarative main (project subcommand 'tomlish' with the -help injection as a declared handler; noargs/unknownfirst -> tomlish), boot core hand-seeded (master _config/punkboot_core.tcl + _vfscommon punkboot/core.tcl - its older make.tcl's vfscommonupdate leaves punkboot/ untouched), kit rebaked with tomlish's own make.tcl ('vfs' command, mapvfs.config, tclsfe-bb 9.1b0 runtime, zipcat type). Behaviour parity verified against captured old-kit baseline: bare -> tomlish help, -help -> help, file argument -> identical app-tomlish handling; deliberate deltas recorded in the thin main header (standard subcommands reachable; current mode vocabulary). tomlish fossil commit bbc68a3f. Follow-on found there and landed: core 0.2.1 graceful missing-app-package arms (shellspy/punk/script/shell report the missing package + exit 1 - proven in tomlish, replayed identically to the punkshell master, 0.47.1). Acceptance tally at flip: parity on punkshell kits (full punkexe suite, kitmountpoint across three runtime families, 11-kit bakes) - cookfs branch carried verbatim with no mapped kit to exercise (none existed before the split either, recorded above); edit-only-the-thin-main proven in a generated project AND tomlish; layout-owned delivery via _vfscommon + layout channels; core versioned and kit-reported via the G-025 buildinfo surfaces (stamp + live); guidance shipped (sample, READMEs, AGENTS.md, runtime-queryable moduledoc::punkboot); collision gate live across every bake this arc ran.
Follow-ons
Follow-on: make.tcl shell adopts the punk executable launch surface by sourcing the shared boot core (plain runtime + checkout presents the same surface as a kit) => open Follow-on: graceful missing-app-package subcommand arms found during the tomlish demonstration => landed 2026-08-02 (boot core 0.2.1, punkshell 0.47.1)