You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

8.1 KiB

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) 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/.

Context

Before 2026-07-27 the tree held several byte-identical punk1.ico files with nothing above them. An .ico is a compiled artifact - a container of pre-rasterized bitmaps - so there was no file anyone could edit, no recorded way to produce a new one, and no way to verify that any copy was what someone intended.

src/assets/logo/ (91fa500e) introduced a vector master and a generator, establishing three tiers: the SVG master, the generated .ico, and the build-consumed copies (src/runtime/punkshell.ico, _vfscommon.vfs, mkzipfix.vfs, punk8_statictwapi.vfs). What is still missing is any record that ties the tiers together. Nothing states that src/runtime/punkshell.ico came from punk-mark.svg, so one specific failure is undetectable: someone edits the SVG, forgets to regenerate, and the committed .ico silently stops depicting the master.

Design-time and bake-time are separate problems, and conflating them is what makes the manual copy into build trees feel wrong when it is not. G-057's acceptance requires that "a fresh checkout that fetches a runtime and bakes produces iconed kits with no build toolchain installed" - a rasterizer IS a build toolchain, so SVG-to-raster can never happen at bake time on any platform, including Windows. The .ico must remain a committed artifact. What is missing is therefore not automation but a recorded, checkable link.

The generator's platform tie is narrower than it appears. pwsh and rsvg-convert are both cross-platform; the entire Windows dependency is one line - Add-Type -AssemblyName System.Drawing, used solely to decode a PNG into raw BGRA pixels, which throws on non-Windows under .NET 6+. Tcl core ships zlib, so PNG decode is inflate plus undoing the five row filters, and the ICO container assembly downstream is pure binary format.

Approach

  • 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.

  • 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:

    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

    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 future embedding step can refuse anything marked role = "web".

  • Generation and verification are separable and get different treatment. Verification must be portable and dependency-free, because it is what runs everywhere - including inside a bake and inside a derived project. Generation needs a rasterizer and runs only when artwork changes, so it may depend on rsvg-convert while still carrying no Windows-only component.

Alternatives considered

  • PROVENANCE.md prose, the src/tools/punkzip convention - rejected: human prose is not machine-checkable, and the failure this goal targets is silent drift, which needs hashes.
  • 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.
  • 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 .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 Scope names src/runtime/punkshell.ico as the project default, which this goal reframes as a recorded derived copy rather than the origin.
  • Related: G-128 - sibling surface. Portable PE resource stamping shares this goal's motivation that icon handling must not be Windows-bound.
  • The advisory posture the Acceptance borrows is G-133's (achieved 2026-07-27, archived at goals/archive/G-133-bake-payload-consistency-checks.md): its bake payload checks recap as BUILD-WARNINGs and never fail the build.
  • Generator and checker have different homes on purpose. The generator operates on the files beside it, runs only when artwork changes, and is already documented in src/assets/logo/AGENTS.md, so it is replaced in place. The checker is a lint in the shape of goals_lint.tcl and architecture_lint.tcl and belongs in scriptlib/developer/ - unless it becomes a bake step, in which case it must move to a punk:: module, because scriptlib/ is user-only territory that make.tcl must not depend on. That decision is deliberately deferred to the work.
  • Open workflow question, deliberately kept out of the Acceptance: whether the checker runs during make.tcl bake or only as an explicit verification 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.