- **Kit anatomy.** Each `src/vfs/*.vfs` folder is a runtime payload; `src/runtime/mapvfs.config` maps payload folders to platform runtimes under `bin/runtime/<platform>/`; `src/vfs/_vfscommon.vfs/` is a generated merge of common libraries (never hand-edited); `src/vfs/_config/` holds the entry scripts. Sources: `src/vfs/AGENTS.md`, `src/runtime/AGENTS.md`.
- **Entry point.**`src/vfs/_config/punk_main.tcl`. An optional first argument is a dash-delimited package mode composed of tokens from `dev`, `os`, `src`, `internal` (`internal` is always appended when absent), optionally scoped with the `proj:` prefix (G-033: `dev`/`src` resolve against the project containing the cwd - walk-up to the nearest git/fossil repo root with a punkshell-style src tree - instead of the executable's own project; discovery is always reported, never a silent rebind). The next argument is the subcommand: `shell`, `script`, `punk`, `tclsh`, `shellspy`. Any other first argument is treated as a script invocation; no arguments at all means interactive shell. Subcommands map to app packages under `src/lib/` (`app-punkshell`, `app-punkscript`, `app-repl`, `app-shellspy`).
- **Entry point.**`src/vfs/_config/punk_main.tcl`. An optional first argument is a dash-delimited package mode composed of tokens from `dev`, `os`, `src`, `internal` (`internal` is always appended when absent). The next argument is the subcommand: `shell`, `script`, `punk`, `tclsh`, `shellspy`. Any other first argument is treated as a script invocation; no arguments at all means interactive shell. Subcommands map to app packages under `src/lib/` (`app-punkshell`, `app-punkscript`, `app-repl`, `app-shellspy`).
- **Package modes pick module provenance.**`internal` uses module snapshots baked into the kit at build time; `dev` loads build outputs from repo-root `modules/` and `lib/`; `src` loads the unbuilt working tree (`src/modules`, `src/lib`, `src/bootsupport`, `src/vendormodules`) with `package prefer latest` so magic-version dev modules (`999999.0a1.0`) beat stamped snapshots. `src` mode is the standard way to verify working-tree changes without a rebuild. Source: `bin/AGENTS.md` "Launch package modes".
- **Payload mount derivation (G-129, achieved).** The boot keys zipfs presence on `tcl::zipfs::mount` and derives where the executable's attached archive actually mounted from the mount table itself (the entry whose archive file is the executable; fallback: the mount containing the boot script) instead of assuming `tcl::zipfs::root` + `//zipfs:/app`. Modern runtimes still answer `//zipfs:/app`; the androwish/undroidwish 8.6 backport has no `root` command, mounts at the executable's own path, and boots the same payload through its `app/main.tcl` in-archive hook (`src/vfs/punk8win.vfs` carries fauxlinks for both conventions). A mounted archive the boot cannot attribute is reported on stderr rather than silently yielding no internal paths. The derivation procs stay defined post-boot (`::punkboot::zipfs_kit_mountbase`). Sources: `src/vfs/AGENTS.md`, `bin/AGENTS.md` "Kit-wrappable runtime requirements", `src/tests/shell/testsuites/punkexe/kitmountpoint.test`.
- **Static package capture (G-058, achieved).** Boot probe-loads the runtime's statically linked packages in a throwaway interp and records what each provides; every interp the shell fabricates (boot, codethread, shellthread workers) seeds `package ifneeded <name> <ver> {load {} <prefix>}` from that record, and `punk::packagepreference` resolves static-vs-bundled version-aware. Sources: `src/vfs/AGENTS.md`, `goals/archive/G-058-static-runtime-packages.md`.
@ -106,7 +106,7 @@ Mechanics here are documented as pointers only - detailed prose would be obsolet
@ -32,10 +32,6 @@ Acceptance: from a running punk session's code interp, without `repl eval`: `con
Scope: src/vfs/_config/punk_main.tcl; src/lib/app-punkshell/punkshell.tcl (script path or a leaner dedicated app package)
Acceptance: piping commands to `<punkexe> script` runs them and terminates at stdin EOF with no trailing `exit` required, exit code 0 on success; a failing piped command terminates the process with a nonzero exit code and the error on stderr, never landing in an interactive shell regardless of console availability or PUNK_PIPE_EOF; `<punkexe> script <file> [<args>...]` executes the file with conventional ::argv0/::argv and propagates its error status the same way; the script path installs none of the `shell` subcommand's shellfilter stacks/transforms and the launch plumbing itself emits nothing on stdout/stderr (the current stub's stderr diagnostics removed) so exec-style callers see only the script's own output; the motivating example works with no package require boilerplate: piping `dev projects.work *<name>*` to `<punkexe> script` emits the matching-project table and exits 0, because the script interp carries the default punk shell module/alias environment.
### G-033 [achieved 2026-07-28] `proj:` package-mode scope prefix: visitor binary resolves dev/src against the cwd's project → detail: goals/archive/G-033-proj-mode-cwd-project.md
Acceptance: `<installed-punkexe> proj:internal-src shell` (the documented canonical visitor invocation - kit copies win same-version ties, protecting the visiting shell's infrastructure; `proj:src` is the documented faithful-vintage variant where the project's copies win) launched from within a punk project's tree discovers the project root by walking up from cwd to the nearest VCS repo root and feeds it to the existing src-mode path machinery (src/modules, src/bootsupport/modules, src/vendormodules on the module path, src/lib on auto_path), so the session loads the project's dev-versioned modules while ordinary version resolution still lets the project supply anything the kit lacks or exceeds; the launch reports the detected project root and effective path precedence (never a silent rebind); with no project found walking up from cwd, or a `proj:` string containing no root-using block (dev/src), it warns and proceeds without false rebind; proven for a standalone binary outside any source tree (internal = kit contents regardless of binary location) against a project that builds no executable (tomlish is the natural first); exe-relative `src`/`dev` discovery is unchanged for a binary that IS in a project's bin/; the packagemode help text drafted in the detail file becomes the live punk::args documentation when implemented (rendered via G-032).
### G-036 [achieved 2026-07-08] Root-cause the Tcl 9 console+udp worker-thread event-loop wedge; minimal repro for possible upstream reporting → detail: goals/archive/G-036-tcl9-udp-console-worker-wedge.md
Scope: src/modules/shellthread-999999.0a1.0.tm; src/bootsupport/modules/shellthread-1.6.2.tm; src/modules/shellfilter-999999.0a1.0.tm (as characterised - no product-code changes required by this goal; the punkshell mitigations are separate fixes)
Acceptance: (reworked 2026-07-08 after the root cause was found) the wedge mechanism is identified and written up in the detail file - DONE: bundled tcludp 1.0.12's Windows per-thread UDP_ExitProc closes the process-global synchronization events, proven by dump handle-table analysis plus a live CloseHandle breakpoint and confirmed by the upstream 1.0.12->1.0.13 diff, which already fixes it (so no upstream report is required; the standalone minimal repro originally required here is waived as moot by the user); the tcl9 kits bundle tcludp >= 1.0.13 with the in-context batch harness baseline resolved - DONE 2026-07-08 (run-2 syslog workers alive vs the 4/4-wedged 1.0.12 baseline); DONE 2026-07-08 (punk::lib 0.3.0 has_libbug_udp_threadexit, surfaced via 'help tcl' in punk 0.2.1): a has_bug-style detection in the punkshell check machinery (in the vein of punk::lib::check::has_tclbug_* / punk::console::check::has_bug_*, surfaced through the same reporting as 'help tcl'/'help console') reports the vulnerable combination - simple version-based detection (loaded/bundled tcludp <1.0.13onTcl9Windows)issufficient,nobehaviouralprobeneeded;loose-enddecisions(punk8win8.6kit'sudp1.0.12swap;optionalupstreamticketsforresidualtcludptrunkweaknesses)recordedinthedetailfilewhenmade(openasof2026-07-08-non-gating).
### G-135 [proposed] Asset provenance records: a verifiable master-to-derived icon chain with a platform-neutral generator
Scope: src/assets/logo/ (per-asset *.assetorigin.toml sidecars, masters and generated icons); src/assets/logo/make-ico.ps1 (replaced in place by a Tcl generator, then removed); scriptlib/developer/ (provenance checker, sibling to goals_lint and architecture_lint); checker home revisited if it becomes a bake step (a punk:: module rather than scriptlib, which make.tcl must not depend on); src/runtime/punkshell.ico and src/vfs/_vfscommon.vfs/punkshell.ico (recorded as derived copies, not relocated)
Scope: src/assets/logo/ (assetorigin.toml records, masters and generated icons); src/assets/logo/make-ico.ps1 (replaced in place by a Tcl generator, then removed); scriptlib/developer/ (provenance checker, sibling to goals_lint and architecture_lint); checker home revisited if it becomes a bake step (a punk:: module rather than scriptlib, which make.tcl must not depend on); src/runtime/punkshell.ico and src/vfs/_vfscommon.vfs/punkshell.ico (recorded as derived copies, not relocated)
Detail: goals/G-135-asset-provenance-records.md
### G-136 [proposed] Generated projects are seeded with a default icon from a single punkshell-held master
@ -87,7 +87,7 @@ platform-specific difference left in examples is path separator style
### Launch package modes (built punk shells)
The first argument to a built punk shell may be a dash-delimited package mode composed of tokens from `dev`, `os`, `src`, `internal` (e.g. `punksys src`, `punk902z dev-os`), optionally scoped with the `proj:` prefix (e.g. `punk91 proj:internal-src`). `internal` is the default and is always appended when absent. A first argument that is not a valid mode list is treated as a subcommand instead (a `proj:` prefix on a non-mode string is not consumed either). Implementation: `src/vfs/_config/punk_main.tcl` (search `all_package_modes`). The full packagemode contract (block meanings, ordering, proj: examples) is the punk::args documentation at id `(script)::punkexe` (module `punk::args::moduledoc::punkexe`).
The first argument to a built punk shell may be a dash-delimited package mode composed of tokens from `dev`, `os`, `src`, `internal` (e.g. `punksys src`, `punk902z dev-os`). `internal` is the default and is always appended when absent. A first argument that is not a valid mode list is treated as a subcommand instead. Implementation: `src/vfs/_config/punk_main.tcl` (search `all_package_modes`).
`src` mode is the one that matters for verifying working-tree changes:
@ -95,12 +95,6 @@ The first argument to a built punk shell may be a dash-delimited package mode co
- Prepends `src/modules`, `src/modules_tcl<N>`, `src/bootsupport/modules{,_tcl<N>}` and `src/vendormodules{,_tcl<N>}` to the module path, sets `package prefer latest` so dev-numbered `999999.0a1.0` source modules beat kit-stamped snapshots on unversioned `package require`, and registers `#modpod` modules from `src/modules` (startup notice: `src mode: registered N #modpod modules from .../src/modules`).
- Consequence: `punksys src` / `punk902z src` exercises current working-tree source with no kit rebuild; a plain launch (`punksys`) exercises the module snapshots baked into the kit at build time. `package present <pkg>` reporting `999999.0a1.0` (vs a release version like `0.7.1`) tells you which set a session is running - runbooks with version expectations must state the intended launch mode.
`proj:` scope (G-033, achieved - see goals/archive/G-033-proj-mode-cwd-project.md) makes `dev`/`src` resolve against the project containing the CURRENT DIRECTORY instead of the executable's own project - visitor mode for an installed/standalone punk binary exploring a project that builds no shell of its own (the project may be modules-only, e.g. tomlish):
- Root discovery walks up from the cwd to the nearest git/fossil repo root that looks like a punkshell-style project (has `./src` plus one of `src/modules`, `src/vfs`, `src/scriptapps`, or `punkproject.toml` - a lean boot mirror of `punk::repo::is_project_root`).
- The launch always reports the detected root and effective mode precedence on stderr; when no project is found it warns and proceeds with dev/src unbound (never a silent or false rebind, no exe-relative fallback). `proj:` with no dev/src block warns that it has no effect.
- Canonical visitor invocation: `<punkexe> proj:internal-src shell` - kit copies win same-version ties, protecting the visiting shell's own infrastructure. `proj:src` is the faithful-vintage variant where the visited project's copies win ties. The project's genuinely-own code (dev-versioned or absent from the kit) loads from the project in either order.
### Kit-wrappable runtime requirements (G-129)
What a runtime must provide for `make.tcl bake` to wrap it as a punk ZIP kit and for that
Goal: a `proj:` prefix on the package-mode string (e.g. `punkshell proj:internal-src shell`) makes the dev/src path blocks resolve against the punk project containing the current working directory - walking up to the nearest VCS repo root, the marker punk::repo::is_project_root already uses - instead of the executable's own project, so an installed ("visitor") punkshell binary, including one downloaded standalone with no source tree around it, can interactively explore a project that builds no shell-capable binary or no binary at all; the prefix scopes WHICH project, staying outside the ordered dash-list whose block order remains the same-version tie-break dial, and the explicit prefix gates the behaviour so no `cd` silently rebinds a normal launch.
Acceptance: `<installed-punkexe> proj:internal-src shell` (the documented canonical visitor invocation - kit copies win same-version ties, protecting the visiting shell's infrastructure; `proj:src` is the documented faithful-vintage variant where the project's copies win) launched from within a punk project's tree discovers the project root by walking up from cwd to the nearest VCS repo root and feeds it to the existing src-mode path machinery (src/modules, src/bootsupport/modules, src/vendormodules on the module path, src/lib on auto_path), so the session loads the project's dev-versioned modules while ordinary version resolution still lets the project supply anything the kit lacks or exceeds; the launch reports the detected project root and effective path precedence (never a silent rebind); with no project found walking up from cwd, or a `proj:` string containing no root-using block (dev/src), it warns and proceeds without false rebind; proven for a standalone binary outside any source tree (internal = kit contents regardless of binary location) against a project that builds no executable (tomlish is the natural first); exe-relative `src`/`dev` discovery is unchanged for a binary that IS in a project's bin/; the packagemode help text drafted in the detail file becomes the live punk::args documentation when implemented (rendered via G-032).
@ -188,54 +188,3 @@ ships with the prefix, not before):
already flag that `proj:... script` benefits; the two share the same root walk
and the same never-silent rebind rule.
- Archived-goal references in this file: G-015 achieved 2026-07-07 (goals/archive/G-015-script-subcommand-piped-stdin.md).
- Related: G-043 references this goal one-directionally (visitor binaries: shellproject-anchored
resources vs cwd-project resources) - relationship recorded at activation (2026-07-28
goals_xref survey; the other moderate-score overlaps G-123/G-127/G-131 share only
punk_main.tcl/bin co-location, no proj: semantics).
## Progress
Implemented and verified 2026-07-28 (activation and achievement same day; punkshell
# G-135 Asset provenance records: a verifiable master-to-derived icon chain with a platform-neutral generator
Status: proposed
Scope: src/assets/logo/ (per-asset *.assetorigin.toml sidecars, masters and generated icons); src/assets/logo/make-ico.ps1 (replaced in place by a Tcl generator, then removed); scriptlib/developer/ (provenance checker, sibling to goals_lint and architecture_lint); checker home revisited if it becomes a bake step (a punk:: module rather than scriptlib, which make.tcl must not depend on); src/runtime/punkshell.ico and src/vfs/_vfscommon.vfs/punkshell.ico (recorded as derived copies, not relocated)
Scope: src/assets/logo/ (assetorigin.toml records, masters and generated icons); src/assets/logo/make-ico.ps1 (replaced in place by a Tcl generator, then removed); scriptlib/developer/ (provenance checker, sibling to goals_lint and architecture_lint); checker home revisited if it becomes a bake step (a punk:: module rather than scriptlib, which make.tcl must not depend on); src/runtime/punkshell.ico and src/vfs/_vfscommon.vfs/punkshell.ico (recorded as derived copies, not relocated)
Goal: Any committed icon in the tree can state what it was derived from and be checked against that claim on any platform, and regenerating one from its master needs no Windows-only tooling - while an icon carrying no record stays a fully supported and silent case.
Acceptance: the `<asset-filename>.assetorigin.toml`sidecar format is documented with a `schema` key, the recorded asset bound by the sidecar's own filename (suffix stripped, same directory) and the source reference relative to that directory, files ending in the suffix being records, never assets; a reader tolerates a missing sidecar, unknown keys and an unrecognised schema version without erroring; a checker written in plain Tcl - no rasterizer, no external binary, runnable under any punkshell runtime - classifies each asset it examines as exactly one of `unrecorded` (no sidecar), `verified`, `artifact-absent` (a sidecar whose paired asset is gone), `source-absent`, `replaced` or `stale` per the state table in this file, where `verified` requires every hash the sidecar records to match, so a sidecar carrying only the artifact hash is verified on that alone, demonstrably distinguishing `stale` (source changed since the artifact was generated) from `replaced` (artifact changed since recording) and from `artifact-absent`on constructed fixtures, and is advisory only, never failing a build, in the manner of the G-133 payload checks; the icon generator is reimplemented with no Windows-only component (`System.Drawing` in particular), regenerating every generated `.ico` under `src/assets/logo/` byte-identically to the current `make-ico.ps1` under the invocation profile that produced it before that script is removed, and (re)writing the sidecar of each artifact it produces - artifact and source hashes plus the invocation options - so regeneration cannot leave a stale record; and sidecars exist and verify for every generated artifact under `src/assets/logo/`, `alternative/`, `web/` and `project-default/`.
Acceptance: `assetorigin.toml` is documented with a `schema` key and directory-scoped relative paths, and a reader tolerates an absent file, unknown keys and an unrecognised schema version without erroring; a checker written in plain Tcl - no rasterizer, no external binary, runnable under any punkshell runtime - classifies each recorded asset as exactly one of `unrecorded`, `verified`, `source-absent`, `replaced` or `stale`, demonstrably distinguishing `stale` (source changed since the artifact was generated) from `replaced` (artifact changed since recording) on constructed fixtures, and is advisory only, never failing a build, in the manner of the G-133 payload checks; the icon generator is reimplemented with no Windows-only component (`System.Drawing` in particular), producing byte-identical `.ico` output to the current `make-ico.ps1` for every master under `src/assets/logo/` before that script is removed; and records exist and verify for `src/assets/logo/`, `alternative/`, `web/` and `project-default/`.
## Context
@ -42,39 +42,27 @@ container assembly downstream is pure `binary format`.
- The record is OPTIONAL by construction. Absence means `unrecorded` - silent,
never a warning. A record is an opt-in claim, and nothing may read it to decide
whether a build proceeds. Most projects live in this state permanently.
- Per-asset sidecar records: `<asset-filename>.assetorigin.toml` beside the
asset it describes. The pairing is the filename - no `path` key - so create,
replace and remove are always the file pair, with no shared record to edit;
the pair survives wholesale directory copies (the property G-136 depends on)
and single-asset copies alike; and overlay merges compose correctly by
construction (merge_over's same-path replacement carries asset and sidecar
together). Files ending in `.assetorigin.toml` are records, never assets.
- Every field except the artifact `hash` is optional. A sidecar carrying only
a hash means "I know this file, I do not know where it came from" - which
serves an owner who drops in their own `.ico` but still wants change
detection.
- Directory-scoped `assetorigin.toml` carrying an `[[asset]]` array with
RELATIVE paths, so it survives being copied wholesale into a derived tree with
no rewriting (the property G-136 depends on).
- Every field except `path` is optional. An entry carrying only a path and a
hash means "I know this file, I do not know where it came from" - which serves
an owner who drops in their own `.ico` but still wants change detection.
- Unknown keys and unrecognised `schema` versions are skipped, not errors, so a
future writer cannot break an older reader.
- Six states, evaluated in table order with the first match winning (which is
what makes "exactly one" true by construction), policy left to the consumer
rather than baked into the format:
- Five states, with policy left to the consumer rather than baked into the
format:
| Order | State | Condition |
|---|---|---|
| 1 | `unrecorded` | no sidecar for the file |
| 2 | `artifact-absent` | a sidecar exists but its paired asset does not |
| 3 | `replaced` | asset present, hash differs from the sidecar (regardless of source state) |
| 4 | `source-absent` | asset matches, a recorded source is not present in the tree |
| 5 | `stale` | asset matches, recorded source present but its hash differs |
| 6 | `verified` | asset matches, and every other hash the sidecar records also matches |
| State | Condition |
|---|---|
| `unrecorded` | no record file |
| `verified` | artifact and source hashes both match |
| `source-absent` | artifact matches, source not present in the tree |
| `replaced` | artifact hash differs from the record |
| `stale` | artifact matches the record, source present but changed |
Hash comparisons apply only to hashes actually recorded - a claim with no
hash contributes only a presence check, so a source named without a source
hash can never produce `stale`; drift on unhashed claims is the record's own
limitation, not the checker's. `stale` is the point of the whole goal; it is
distinguishable from `replaced`, which means the opposite thing and deserves
the opposite response, and from `artifact-absent`, which means the record
itself has gone stale and needs repair.
`stale` is the point of the whole goal; it is also distinguishable from
`replaced`, which means the opposite thing and deserves the opposite response.
- A `role` field (`pe-resource` | `web` | `generic`) makes an existing hazard
machine-checkable rather than a documentation footnote: `web/favicon.ico` is
all-PNG, which is exactly what GDI+ and tklib's `ico` package cannot read, so a
@ -93,34 +81,15 @@ container assembly downstream is pure `binary format`.
- `punkorigin.toml`, G-027's generated-project carrier - rejected: that answers
project lineage ("this project came from punkshell layout X"), a different
question from asset derivation. Keep them separate.
- One record file per directory carrying an `[[asset]]` array - the original
draft shape - rejected at the 2026-07-28 pre-activation review: entries are
shared state (every writer does a read-modify-write - the punkcheck lesson:
G-094 + G-095, both achieved 2026-07-21, archived at
goals/archive/G-094-punkcheck-single-lifecycle.md and
goals/archive/G-095-punkcheck-concurrent-writer-safety.md), a record at a
.vfs root is clobbered
file-level by overlay merges (a kit-tree record would replace the common one
wholesale, entries and all), dangling entries are invisible in listings, and
copying a single asset out of a directory loses its record. Accepted in
exchange: `web/` carries one sidecar per generated raster, and per-directory
facts (format documentation, cross-asset notes) live in AGENTS.md rather
than in the record file.
- A fauxlink-carried record (toml body per the fauxlink extension convention,
source path encoded in the name) - rejected: the filename would encode a
mutable fact, so master renames invalidate sidecar filenames, and overlay
pairing would need a nominal-keyed composition invariant in merge_over and
every other composer; the ls-visible derivation edge does not pay for that.
The nominal-keyed idea itself (at most one fauxlink per effective nominal
per directory, last layer wins) is recorded as generally useful fauxlink
hardening independent of this goal - the tree already satisfies it (71
fauxlinks, no same-directory nominal collisions, surveyed 2026-07-28).
- Per-file sidecars instead of one record per directory - rejected:
`src/assets/logo/web/` alone would carry six, and a directory record with
relative paths is what makes verbatim copying work.
- Making the `.ico` a build output regenerated at bake time - rejected: G-057
requires bakes to succeed with no build toolchain installed.
## Notes
- Related: G-057 - consumer. Its per-kit icon sidecar is specified to carry "source
- Related: G-057 - consumer. Its per-kit sidecar is specified to carry "source
.ico identity and provenance" in a documented plain-text form; these records
are the natural thing it reads to populate those fields, which closes the gap
where its provenance otherwise stops one tier short of the actual source. Its
@ -144,21 +113,3 @@ container assembly downstream is pure `binary format`.
subcommand. `stale` is a design-time mistake rather than a build-time one, which
argues for a subcommand plus an opportunistic check only when a bake actually
consumes an icon.
- Override behaviour in baked kits (recorded 2026-07-28): a kit .vfs that
overrides a common asset without shipping its own sidecar produces a kit
where the travelled common sidecar classifies the override as `replaced` -
truthful and intended. An overrider that wants `verified` inside the built
artifact ships its own sidecar beside the override; merge_over carries the
pair by same-path replacement with no new machinery.
- The sidecar shape eases the checker's parsing posture: each record is one
small flat toml table, so a self-contained subset reader stays defensible if
the tomlish dependency is unwanted in the scriptlib context - the checker's
toml/hash dependency posture is still an open decision for the work
(pre-activation review 2026-07-28).
- Open (pre-activation review 2026-07-28): whether sidecars for the
build-consumed `punkshell.ico` copies are acceptance-required - Scope names
two of the four (`src/runtime/`, `_vfscommon.vfs`; `mkzipfix.vfs` and
`punk8_statictwapi.vfs` carry byte-identical copies too) - and the explicit
decision that a sidecar committed in `_vfscommon.vfs` ships inside every
baked kit (the per-asset shape makes that collision-free; the shipping
Scope: src/assets/logo/project-default/ (the single held seed artwork); src/modules/punk/mix/commandset/project-999999.0a1.0.tm (generation-time asset injection beside bootsupport_inject); src/project_layouts/ (per-layout icon-slot declaration with no artwork stored in layout trees); src/modules/punkcheck-999999.0a1.0.tm (install records for injected assets)
Goal: A newly generated project arrives with a working and inspectable icon - a readable vector master plus a ready-to-use `.ico` - injected from punkshell's single held copy at generation time, with no icon artwork stored in any layout payload tree, and regeneration never silently discards an owner's replacement.
Acceptance: `dev project.new` against a layout declaring an icon slot produces `src/assets/logo/` in the generated project containing `icon.svg`, `icon.ico` and `icon.ico.assetorigin.toml` that the G-135 checker reports as `verified` in that tree (artifact and recorded source both present, hashes matching), with the artwork sourced from `src/assets/logo/project-default/` at generation time rather than from any file under `src/project_layouts/` - verified by that tree containing no icon `.svg` or `.ico` payload; each injected file is recorded as a punkcheck install event in the same manner as `bootsupport_inject`; a layout declaring no icon slot generates a project with no `src/assets/logo/` and no warning; and re-running generation over a project whose `icon.svg` or `icon.ico` differs from the injected original leaves the owner's file intact and reports the skip by name.
Acceptance: `dev project.new` against a layout declaring an icon slot produces `src/assets/logo/` in the generated project containing `icon.svg`, `icon.ico` and an `assetorigin.toml` that the G-135 checker reports as `verified` in that tree (source and artifact both present, hashes matching), with the artwork sourced from `src/assets/logo/project-default/` at generation time rather than from any file under `src/project_layouts/` - verified by that tree containing no icon `.svg` or `.ico` payload; each injected file is recorded as a punkcheck install event in the same manner as `bootsupport_inject`; a layout declaring no icon slot generates a project with no `src/assets/logo/` and no warning; and re-running generation over a project whose `icon.svg` or `icon.ico` differs from the injected original leaves the owner's file intact and reports the skip by name.
## Context
@ -42,11 +42,10 @@ unbranded so it reads "replace me" rather than as a product identity.
a rasterizer, and requiring one of a fresh project would defeat the purpose;
the pair also gives the owner a complete worked example of the master-to-derived
relationship, which no amount of documentation prose achieves.
- The `icon.ico.assetorigin.toml` sidecar copies verbatim. Because G-135's
record is a per-asset sidecar bound by its own filename with
directory-relative source references, it needs no rewriting at the
destination and reads `verified` there - artifact and recorded source are
both present in the generated tree.
- The `assetorigin.toml` copies verbatim. Because G-135's record is
directory-scoped with relative paths, it needs no rewriting at the destination
and reads `verified` there - source and artifact are both present in the
generated tree.
- Per-layout icon-slot declaration. A layout may decline, and declining is not a
failure or a warning.
- Never clobber a replacement. Re-running generation over a project whose icon
@ -71,10 +70,9 @@ unbranded so it reads "replace me" rather than as a product identity.
## Notes
- Related: G-135 - depends on. Consumes the sidecar record format and the
checker, and relies specifically on the record being a per-asset sidecar
with directory-relative references so it survives injection unchanged (and
a single asset re-injected travels with its record as a pair).
- Related: G-135 - depends on. Consumes the `assetorigin.toml` format and the
checker, and relies specifically on the record being directory-scoped with
relative paths so it survives injection unchanged.
- Related: G-027 - consumer. Pull-based infrastructure updates for derived
projects would later refresh seeded assets, and both goals record their
@ -16,7 +16,7 @@ VFS (Virtual File System) folders define the runtime payloads that get wrapped i
- A `*.vfs` folder built against a runtime is expected to carry a root-level startup script: an actual `main.tcl`, or a root fauxlink resolving to the name `main.tcl` whose target exists and is a `.tcl` file. `make.tcl` warns when neither is present (column-0 `BUILD-WARNING:` token, ANSI-highlighted, recapped at end of run like provenance warnings) but still builds - a kit without a startup script is legal. Overlay-only folders with no runtime mapping (e.g `_vfscommon.vfs`) are not checked.
- VFS content must be compatible with the target platform runtime.
- `_vfscommon.vfs/` is an auto-generated merge of common libraries; manually editing it will be overwritten.
- `_config/punk_main.tcl` accepts an optional package_mode argument before the subcommand. Valid modes are `dev`, `os`, `internal`, and `src`, dash-delimited in priority order (e.g `src-internal`). `internal` is always appended if omitted. An optional `proj:` prefix (G-033, e.g `proj:internal-src`) makes `dev`/`src` resolve against the project containing the cwd (walk-up to the nearest git/fossil repo root with a punkshell-style src tree, via `::punkboot::proj_root_find`) instead of the executable's own project; discovery outcome is always reported on stderr and a failed discovery leaves dev/src unbound rather than falling back exe-relative.
- `_config/punk_main.tcl` accepts an optional package_mode argument before the subcommand. Valid modes are `dev`, `os`, `internal`, and `src`, dash-delimited in priority order (e.g `src-internal`). `internal` is always appended if omitted.
- `_config/punk_main.tcl` captures the runtime's static/builtin packages at boot (G-058): empty-filename `info loaded` prefixes are probe-loaded in a throwaway interp (denylist `::punkboot::static_probe_denylist`, default `tk* vfs mk4tcl vlerq tdbc*` - side-effecting or composite inits) and each package a probe-load actually PROVIDES is recorded in `::punkboot::static_packages` {pkgname {version <v> prefix <p>}} with `::punkboot::static_prefixes` alongside. The boot interp and every punkshell-fabricated interp/thread (punk::lib::interp_sync_package_paths / snapshot_package_paths, repl codethread, shellthread workers) seed `package ifneeded <name> <ver> {load {} <prefix>}` from it, and punk::packagepreference resolves static-vs-bundled version-aware. Composite statics whose script layer lived in the runtime's own (replaced) zipfs provide nothing under the probe and are correctly left to bundled copies.
- `src` mode loads unbuilt modules and libraries from the project's `src/` tree (`src/modules`, `src/lib`, `src/bootsupport`, `src/vendormodules`) instead of built output. It also runs an inline `#modpod` package-ifneeded scanner (using Tcl builtins only, since no punk modules are loaded at boot time) and sets `package prefer latest` so `999999.0a1.0` dev modules are preferred over stable bootsupport/vendored copies.
- `_config/punk_main.tcl` and `_config/project_main.tcl` derive the kit's payload location from the runtime's own zipfs mount table (G-129): zipfs presence keys on `tcl::zipfs::mount` (not `tcl::zipfs::root`, which the androwish/undroidwish 8.6 backport lacks), the mount base is the table entry whose archive file is `[info nameofexecutable]` (fallback: the mount containing the boot script), `//zipfs:/app` stays the expected modern answer rather than a compiled-in assumption, and a mounted archive that cannot be attributed to the executable is reported on stderr. The derivation lives in `::punkboot` procs (`zipfs_kit_mountbase`, `zipfs_mount_pairs`, `path_within`) that remain defined after boot for probing and tests. Both files carry identical copies of this logic (G-031 is the planned consolidation). Characterization: `src/tests/shell/testsuites/punkexe/kitmountpoint.test`.
#(the record keys are -ts_begin/-ts_end - passing the raw record gave the constructor's
# -tsbegin/-tsend defaults, so reconstructed events took 'now' as begin and empty end,
# and any later flush/save_installer_record rewrote prior event history with those values)
set eid [punkcheck::dict_getwithdefault $e -id ""]
#self-heal damaged persisted history rather than abort the caller's operation:
#files written by punkcheck < 0.3.2 carry EVENT records whose -id (and
#-source/-targets/-config) hold the LITERAL strings of the old writer's braced
#template ('$eventid' etc - the 0.3.2 buildversion note fixed the writer, but
#files keep the damage) and any two such records collide on the duplicate key
#here. History is advisory - reconstruct the record under a synthesized unique
#id and warn naming the file, instead of failing e.g a whole bake's vfslibs
#phase. Synthesized ids match no INSTALL record's -eventid reference, so
#healed events age out through normal -keep_events pruning.
if {$eid eq "" || $eid in [my events keys]} {
set healed_id "damaged-id-[my events count]-[clock microseconds]"
set shown [string range $eid 0 60]
set why [expr {$eid eq "" ? "missing" : "duplicates an earlier event"}]
puts stderr "punkcheck: WARNING damaged event history in '$o_checkfile': event -id '$shown' $why - keeping record under synthesized id '$healed_id' (safe to ignore; delete the file to reset history)"