Browse Source

G-135 + G-136: per-asset assetorigin sidecars replace the directory record

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
master
Julian Noble 2 days ago
parent
commit
b692979c6a
  1. 2
      GOALS.md
  2. 2
      goals/G-057-kit-icon-embedding.md
  3. 95
      goals/G-135-asset-provenance-records.md
  4. 18
      goals/G-136-generated-project-icon-seeding.md

2
GOALS.md

@ -416,7 +416,7 @@ Scope: src/make.tcl (post-assembly offset-style probe at the G-133 advisory-chec
Detail: goals/G-134-baked-kits-pinned-archive-relative.md Detail: goals/G-134-baked-kits-pinned-archive-relative.md
### G-135 [proposed] Asset provenance records: a verifiable master-to-derived icon chain with a platform-neutral generator ### 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 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 ### G-136 [proposed] Generated projects are seeded with a default icon from a single punkshell-held master

2
goals/G-057-kit-icon-embedding.md

@ -131,7 +131,7 @@ delete-then-write). Requirements sketch agreed 2026-07-10:
in the Acceptance that the override is shared across a kit definition's targets. in the Acceptance that the override is shared across a kit definition's targets.
- Related: G-135 - the source-tree provenance records this goal's sidecar can read. The - Related: G-135 - the source-tree provenance records this goal's sidecar can read. The
sidecar is emitted per kit at bake time and describes what got embedded; G-135's sidecar is emitted per kit at bake time and describes what got embedded; G-135's
assetorigin.toml is committed and describes a derivation. Populating the sidecar's per-asset assetorigin sidecars are committed and describe a derivation. Populating the sidecar's
"source .ico identity and provenance" from those records closes the gap where this "source .ico identity and provenance" from those records closes the gap where this
goal's provenance otherwise stops at the .ico bytes, one tier short of the SVG master goal's provenance otherwise stops at the .ico bytes, one tier short of the SVG master
they were generated from. they were generated from.

95
goals/G-135-asset-provenance-records.md

@ -1,9 +1,9 @@
# G-135 Asset provenance records: a verifiable master-to-derived icon chain with a platform-neutral generator # G-135 Asset provenance records: a verifiable master-to-derived icon chain with a platform-neutral generator
Status: proposed 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. 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 ## Context
@ -42,27 +42,39 @@ container assembly downstream is pure `binary format`.
- The record is OPTIONAL by construction. Absence means `unrecorded` - silent, - 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 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. whether a build proceeds. Most projects live in this state permanently.
- Directory-scoped `assetorigin.toml` carrying an `[[asset]]` array with - Per-asset sidecar records: `<asset-filename>.assetorigin.toml` beside the
RELATIVE paths, so it survives being copied wholesale into a derived tree with asset it describes. The pairing is the filename - no `path` key - so create,
no rewriting (the property G-136 depends on). replace and remove are always the file pair, with no shared record to edit;
- Every field except `path` is optional. An entry carrying only a path and a the pair survives wholesale directory copies (the property G-136 depends on)
hash means "I know this file, I do not know where it came from" - which serves and single-asset copies alike; and overlay merges compose correctly by
an owner who drops in their own `.ico` but still wants change detection. 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 - Unknown keys and unrecognised `schema` versions are skipped, not errors, so a
future writer cannot break an older reader. future writer cannot break an older reader.
- Five states, with policy left to the consumer rather than baked into the - Six states, evaluated in table order with the first match winning (which is
format: what makes "exactly one" true by construction), policy left to the consumer
rather than baked into the format:
| State | Condition | | Order | State | Condition |
|---|---| |---|---|---|
| `unrecorded` | no record file | | 1 | `unrecorded` | no sidecar for the file |
| `verified` | artifact and source hashes both match | | 2 | `artifact-absent` | a sidecar exists but its paired asset does not |
| `source-absent` | artifact matches, source not present in the tree | | 3 | `replaced` | asset present, hash differs from the sidecar (regardless of source state) |
| `replaced` | artifact hash differs from the record | | 4 | `source-absent` | asset matches, a recorded source is not present in the tree |
| `stale` | artifact matches the record, source present but changed | | 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 Hash comparisons apply only to hashes actually recorded - a claim with no
`replaced`, which means the opposite thing and deserves the opposite response. 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 - A `role` field (`pe-resource` | `web` | `generic`) makes an existing hazard
machine-checkable rather than a documentation footnote: `web/favicon.ico` is 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 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 - `punkorigin.toml`, G-027's generated-project carrier - rejected: that answers
project lineage ("this project came from punkshell layout X"), a different project lineage ("this project came from punkshell layout X"), a different
question from asset derivation. Keep them separate. question from asset derivation. Keep them separate.
- Per-file sidecars instead of one record per directory - rejected: - One record file per directory carrying an `[[asset]]` array - the original
`src/assets/logo/web/` alone would carry six, and a directory record with draft shape - rejected at the 2026-07-28 pre-activation review: entries are
relative paths is what makes verbatim copying work. 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 - Making the `.ico` a build output regenerated at bake time - rejected: G-057
requires bakes to succeed with no build toolchain installed. requires bakes to succeed with no build toolchain installed.
## Notes ## 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 .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 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 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 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 argues for a subcommand plus an opportunistic check only when a bake actually
consumes an icon. 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
intent itself is the open call).

18
goals/G-136-generated-project-icon-seeding.md

@ -3,7 +3,7 @@
Status: proposed Status: proposed
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) 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. 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 ## 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; 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 the pair also gives the owner a complete worked example of the master-to-derived
relationship, which no amount of documentation prose achieves. relationship, which no amount of documentation prose achieves.
- The `assetorigin.toml` copies verbatim. Because G-135's record is - The `icon.ico.assetorigin.toml` sidecar copies verbatim. Because G-135's
directory-scoped with relative paths, it needs no rewriting at the destination record is a per-asset sidecar bound by its own filename with
and reads `verified` there - source and artifact are both present in the directory-relative source references, it needs no rewriting at the
generated tree. 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 - Per-layout icon-slot declaration. A layout may decline, and declining is not a
failure or a warning. failure or a warning.
- Never clobber a replacement. Re-running generation over a project whose icon - 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 ## Notes
- Related: G-135 - depends on. Consumes the `assetorigin.toml` format and the - Related: G-135 - depends on. Consumes the sidecar record format and the
checker, and relies specifically on the record being directory-scoped with checker, and relies specifically on the record being a per-asset sidecar
relative paths so it survives injection unchanged. 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 - Related: G-027 - consumer. Pull-based infrastructure updates for derived
projects would later refresh seeded assets, and both goals record their projects would later refresh seeded assets, and both goals record their
installs through punkcheck. installs through punkcheck.

Loading…
Cancel
Save