Promotion gate run from a clean tree (both VCS committed at punkshell
0.29.0). Five stamped-version promotions from the deployed modules
tree: punk::args::moduledoc::punkexe 0.1.0 -> 0.1.1 (the G-033
packagemode leader with the proj: contract - kits baked from here on
carry the live launch documentation), plus four bumps pending from
earlier change-sets: argparsingtest 1.2.0 -> 1.3.0, oolib 0.1.3 ->
0.1.4, punk::zip 0.2.0 -> 0.3.0 (punkzip extraction accelerator),
punkcheck 0.6.0 -> 0.6.1 (installtrack self-heal). The
templates-0.2.0.tm modpod refreshed by the same replace (zip .tm,
rides binary-glob; existing artifact updated, no new binaries).
Kits pick these up at their next 'make.tcl bake'.
Claude-Session: https://claude.ai/code/session_0156PuejSCGjgeGb7jiABrDU
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Activation and achievement same day at user direction ('activate and
start on it'). Activation freshness survey (goals_xref score G-033)
recorded in the detail file: G-043's one-directional reference is the
only unlinked real relationship; G-123/G-127/G-131 overlap by
punk_main.tcl/bin co-location only.
Detail file gains the Progress record (option (a) inline-walk
decision, verification evidence on the rebaked punk91 kit, the
moduledoc-0.1.1 live-help note and the pending vfscommonupdate
promotion) and moves content-identical to goals/archive/; index entry
moves GOALS.md -> GOALS-archive.md.
Reference sweep: G-016/G-047 relationship notes point at the archive
and the ::punkboot::proj_root_find delivery; G-032's Approach now
says the packagemode leader text is LIVE and its remaining lane is
rendering/parse wiring; G-089's Related note records that the visitor
root walk it rides is live, remains defined post-boot, and that the
boot-interface packagemode hint it needs is still absent; G-031 gains
the fork-drift datapoint (proj: landed in punk_main.tcl only,
project_main.tcl still lacks src mode and proj:); G-018/G-035/G-043
mentions gain achieved/archive markers.
goals_lint clean (85 active, 51 archived), architecture_lint clean.
Claude-Session: https://claude.ai/code/session_0156PuejSCGjgeGb7jiABrDU
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
punk_main.tcl: an optional proj: prefix on the package-mode string
(e.g 'proj:internal-src') rebinds the dev/src path blocks to the punk
project containing the current directory instead of the executable's
own project. Root discovery is ::punkboot::proj_root_find, a lean
boot mirror of punk::repo::find_project/is_project_root in Tcl
builtins only (git/fossil control markers via 'file exists', unwise-
path exclusion, ./src plus src/modules|src/vfs|src/scriptapps|
punkproject.toml candidate check; .git accepted as file OR dir for
worktree checkouts - recorded divergence). The proc stays defined
post-boot like the G-129 mount procs. The prefix sits outside the
ordered dash-list, whose block order remains the same-version
tie-break dial; an invalid mode remainder is not consumed (whole arg
falls through as subcommand/script as today); bare 'proj:' reads as
internal and warns. Discovery outcome is always reported on stderr
(detected root + effective precedence - never a silent rebind), a
failed walk-up warns and leaves dev/src unbound (no exe-relative
fallback rebind), and proj: without a dev/src block warns it has no
effect. Under proj:, dev resolves <root>/modules{,_tclN} and
<root>/lib{,_tclN} only; src feeds the discovered root into the
existing src-mode machinery unchanged (tm paths, auto_path, package
prefer latest, inline #modpod scanner). Exe-relative src/dev launches
are untouched.
punk::args::moduledoc::punkexe 0.1.1: the packagemode help text
drafted in the goal file is now live as the packagemode leader of
(script)::punkexe (@leaders max 2), with proj: wording in the @cmd
overview; rendering/parse wiring remains G-032.
Verified on a rebaked punk91 kit, standalone copy outside any source
tree: proj:internal-src from tomlish (fossil root, builds no
executable) loads tomlish 999999.0a1.0 from its src/modules with
kit-first ties; proj:src flips the ordering; from punkshell the
visited-root modpod scan registers 6 modpods; no-project and
no-root-using-block warnings fire and proceed; bin/punk91 src
unchanged.
Docs: bin/AGENTS.md launch-package-modes proj: contract,
src/vfs/AGENTS.md punk_main contract line, ARCHITECTURE.md entry
point. punkshell 0.29.0 (minor - new launch invocation) + CHANGELOG.
Claude-Session: https://claude.ai/code/session_0156PuejSCGjgeGb7jiABrDU
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Pre-activation review outcome (user-approved wording): G-135's record
shape pivots from one assetorigin.toml per directory to a per-asset
sidecar, <asset-filename>.assetorigin.toml beside the asset it
describes.
G-135 contract: Scope (both tiers, mirror-identical) names per-asset
*.assetorigin.toml sidecars. Acceptance rebuilt - records are bound by
the sidecar's own filename (suffix stripped, same directory; files
ending in the suffix are records, never assets; reader tolerance kept);
the checker's state model is repaired to six states evaluated in table
order with first match winning (artifact-absent added for records whose
paired asset is gone; verified now requires every hash the sidecar
records to match, so a hash-only sidecar verifies on the artifact hash
alone; fixtures must distinguish stale / replaced / artifact-absent);
the generator clause requires byte-identity per the invocation profile
that produced each .ico and that the generator (re)writes each produced
artifact's sidecar, so regeneration cannot leave a stale record; the
final clause requires sidecars to exist and verify for every generated
artifact under the four asset directories.
G-135 body: Approach carries the pairing rationale (no path key, file-
pair lifecycle, survives wholesale and single-asset copies, composes
under merge_over same-path replacement) and the hash-required minimum;
the six-row state table gains the evaluation-order and unhashed-claims
notes. Alternatives inverted: the per-directory [[asset]] record is now
the rejected shape (shared-state writers - the punkcheck G-094/G-095
lesson; file-level clobber at a .vfs root under overlay merges;
invisible dangling entries; single-asset copies lose their record), and
a fauxlink-carried record is recorded as considered-and-rejected (the
filename would encode a mutable fact; overlay pairing would need a
nominal-keyed composition invariant in every composer), preserving the
nominal-keyed idea as fauxlink hardening independent of this goal (71
fauxlinks in tree, zero same-directory nominal collisions, surveyed
2026-07-28). Notes add the override-without-sidecar kit behaviour
(travelled common sidecar truthfully reads replaced), the eased checker
parsing posture, and the open pre-activation items (which of the four
build-consumed punkshell.ico copies get sidecars; whether a
_vfscommon.vfs sidecar shipping inside every baked kit is the intent).
G-136: acceptance names icon.ico.assetorigin.toml and the per-asset
dependency; Approach/Notes reworded to match. G-057: one Notes sentence
disambiguates its per-kit icon sidecar from G-135's record sidecars.
goals_lint clean (index/detail mirrors exact, archived-goal references
markered); goals_xref score G-135 re-run - G-136 and G-057 linked.
G-135 stays proposed; activation is the user's flip.
Claude-Session: https://claude.ai/code/session_014wNGMsdNFFrU1duADeirgm
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
2 days ago
28 changed files with 820 additions and 309 deletions
- **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). 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), 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`).
- **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,6 +32,10 @@ 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/ (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)
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)
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`). `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`).
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`).
`src` mode is the one that matters for verifying working-tree changes:
@ -95,6 +95,12 @@ 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
# G-135 Asset provenance records: a verifiable master-to-derived icon chain with a platform-neutral generator
Status: proposed
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)
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)
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: `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/`.
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/`.
## Context
@ -42,27 +42,39 @@ 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.
- 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.
- 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.
- Unknown keys and unrecognised `schema` versions are skipped, not errors, so a
future writer cannot break an older reader.
- Five states, with policy left to the consumer rather than baked into the
format:
- 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:
| 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 |
| 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 |
`stale` is the point of the whole goal; it is also distinguishable from
`replaced`, which means the opposite thing and deserves the opposite response.
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.
- 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
@ -81,15 +93,34 @@ 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.
- 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.
- 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).
- 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 sidecar is specified to carry "source
- Related: G-057 - consumer. Its per-kit icon 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
@ -113,3 +144,21 @@ 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 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.
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.
## Context
@ -42,10 +42,11 @@ 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 `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.
- 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.
- 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
@ -70,9 +71,10 @@ unbranded so it reads "replace me" rather than as a product identity.
## Notes
- 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-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-027 - consumer. Pull-based infrastructure updates for derived
projects would later refresh seeded assets, and both goals record their
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,3 +188,54 @@ 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
@ -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.
- `_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` 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)"