Browse Source

add goal G-012: template system - inert VCS-config payloads and explicit layout refresh

Project layouts carry .gitignore files as payload for generated projects,
but git treats any nested .gitignore as live configuration scoped to its
directory - the templates self-censor, silently untracking their own
content in the outer repo (discovered via the git/fossil tracked-set
verification; stopgap force-adds in fc1c474c). G-012 [proposed] requires
payloads stored inert (neutral rename or fauxlink-encoded - mechanism an
implementation decision recorded in the detail file) with materialization
at project generation, plus a punkcheck-tracked make.tcl template-refresh
step generalising the existing sync_layouts (which covers only
build.tcl/make.tcl into custom/_project) to the vendor/punk layout base
and derived payloads. Acceptance covers a provably-inert nested-rule scan,
generated-project content equivalence, a working refresh after a
canonical-source edit, and retiring the per-file force-add exceptions.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 2 weeks ago
parent
commit
49985aaba4
  1. 6
      GOALS.md
  2. 80
      goals/G-012-template-payload-safety.md

6
GOALS.md

@ -115,3 +115,9 @@ Scope: src/modules/punk/console-999999.0a1.0.tm, src/modules/opunk/console-99999
Detail: goals/G-011-console-stderr-semantics.md
Goal: a console optionally carries an err channel as an attribute of its canonical {in out} identity - {in out err} specs accepted everywhere -console is, err resolving to process stderr for the default console and to the console's out channel elsewhere - so diagnostics and emit-to-err are first-class per-console operations instead of raw puts stderr.
Acceptance: console_spec_resolve and every -console site accept an {in out err} spec (err optional; existing pair/instance-name/object spec forms unchanged); opunk::Console exposes the err channel (nullable, additive base-class change); an unset err resolves to stderr for the default console and to the console's own out channel otherwise; punk::console's own warnings/diagnostics emitted while operating on a resolvable console go to that console's err (raw puts stderr remains only where no console is in play); an emit-to-err path exists and is exercised by at least one real consumer (e.g punk::repl); the effective err is discoverable from any thread/interp via console_fact_get (fact key err, returning the effective err channel name); ownership/fact/mode-cache keys remain canonical {in out}; the existing console test suites pass unchanged.
### G-012 [proposed] Template system: inert VCS-config payloads and explicit layout refresh
Scope: src/project_layouts/, src/make.tcl, src/modules/punk/mix/ (layout instantiation), fauxlink module (bootsupport 0.1.1 - promoted if chosen as mechanism)
Detail: goals/G-012-template-payload-safety.md
Goal: project layouts carry no live nested VCS-config files - template .gitignore payloads are stored inert (renamed, or fauxlink-encoded) and materialized at project generation - and src/make.tcl has an explicit punkcheck-tracked step that refreshes layout payloads from their canonical sources.
Acceptance: a scan of src/project_layouts finds no file named .gitignore, and git check-ignore --no-index over every layout file matches only root-.gitignore rules (nested rules provably inert); a project generated from each affected layout receives a working .gitignore whose content matches the canonical payload/target; after editing a canonical source (e.g. root .gitignore), the make.tcl template-refresh step updates the derived layout payloads (punkcheck-tracked), covering vendor/punk layouts as well as custom/_project; the previously hidden template files (layout READMEs, vendored TODO-class files) remain git-tracked without per-file force-add exceptions.

80
goals/G-012-template-payload-safety.md

@ -0,0 +1,80 @@
# G-012 Template system: inert VCS-config payloads and explicit layout refresh
Status: proposed
Scope: src/project_layouts/, src/make.tcl, src/modules/punk/mix/ (layout instantiation), fauxlink module (bootsupport 0.1.1 - promoted if chosen as mechanism)
Acceptance: a scan of src/project_layouts finds no file named .gitignore, and git check-ignore --no-index over every layout file matches only root-.gitignore rules (nested rules provably inert); a project generated from each affected layout receives a working .gitignore whose content matches the canonical payload/target; after editing a canonical source (e.g. root .gitignore), the make.tcl template-refresh step updates the derived layout payloads (punkcheck-tracked), covering vendor/punk layouts as well as custom/_project; the previously hidden template files (layout READMEs, vendored TODO-class files) remain git-tracked without per-file force-add exceptions.
## Context
Project layouts under `src/project_layouts/` carry `.gitignore` files as *payload* - the ignore
file a generated project is supposed to receive (essentially a copy of shellspy's own root
`.gitignore`). But git has no concept of payload: any `.gitignore` anywhere in the working tree
is live configuration for its subtree, with leading-`/` patterns anchored to the nested file's
own directory. The result is a template that self-censors - the layout ignores, in the shellspy
repo, exactly what a generated project would ignore in itself.
Discovered 2026-07-06 during git/fossil tracked-set verification (fossil reads only the
checkout-root `.fossil-settings/ignore-glob`, so it managed template files git silently
dropped): four `vendor/punk/*` layouts each carry a live `.gitignore`, and
`.../project-0.1/{bin,lib,modules}/README.md` plus `bin/runtime/README.md` were untracked in
git. The `bin/runtime` case shows the nastier variant - git does not descend into an excluded
directory, so negation patterns cannot resurrect deeper paths. The immediate stopgap was
per-file `git add -f` (commit fc1c474c), which protects only existing files: any new file
placed in a template's ignored spots drops silently again. The dual-VCS contract
(.fossil-settings/AGENTS.md) records the git/fossil asymmetry and points here for the fix.
A secondary weakness: template payloads that mirror living files (the root `.gitignore`,
build.tcl/make.tcl, bootsupport modules) have only partial refresh machinery. make.tcl's
`sync_layouts` step (punkcheck-tracked) syncs just `build.tcl` and `make.tcl`, and only into
the `custom/_project` layout base - the `vendor/punk/*` layouts have no refresh mechanism, so
payload drift is unmanaged.
## Approach
1. **Store VCS-config payloads inert.** Two candidate mechanisms; the choice is an
implementation decision to be recorded here when made:
- *Neutral rename*: payload stored as e.g. `_gitignore` or `gitignore.in`; layout
instantiation renames it to `.gitignore` in the generated project. Simple, no new
dependency; templates keep a literal copy (drift handled by the refresh step).
- *Fauxlink*: payload stored as `<nominal>#<encoded-target>.fauxlink` (see fauxlink module,
currently bootsupport 0.1.1) and resolved at generation. Inert for git by construction,
and the target can be the canonical root `.gitignore` itself - eliminating copy drift
rather than managing it. Requires generation-time resolution in punk::mix and possibly
promoting fauxlink to a first-class module dependency.
2. **Materialize at generation.** The punk::mix layout-instantiation path produces a real
`.gitignore` in the generated project regardless of mechanism, with content equal to the
canonical payload/target.
3. **Explicit template refresh in make.tcl.** Generalise the existing `sync_layouts` step into
a defined template-refresh covering the `vendor/punk/*` layout base as well as
`custom/_project`, and derived payloads beyond build.tcl/make.tcl (the gitignore payload,
and prospectively other mirrored content). punkcheck-tracked like the existing step, so
refreshes are recorded and skippable when sources are unchanged.
4. **Retire the stopgap.** With inert payloads, the per-file force-adds from fc1c474c become
ordinary tracked files (no ignore rule matches them); the accidental-ignore class is
structurally impossible rather than patched per file.
## Alternatives considered
- **Negation entries inside the nested template .gitignore files** (e.g. `!README.md`) -
rejected: pollutes the payload that generated projects receive, and cannot fix the
excluded-directory case (`bin/runtime/`) because git does not descend into excluded dirs to
evaluate negations.
- **Keep per-file `git add -f` only** - rejected as the durable answer: silent drop recurs for
every new file placed under a template's ignored paths; nothing structural changes.
- **`.git/info/exclude` or repo-local config tricks** - rejected: not versioned/portable to
other clones and contributors.
- **Do nothing on the git side (fossil sees the files)** - rejected: git is the day-to-day VCS;
a mirror having the truer view is a symptom, not a solution.
## Notes
- Affected today: `src/project_layouts/vendor/punk/{basic,minimal,project-0.1,sample-0.1}/.gitignore`.
The same inert-payload treatment should apply to any future live-config payloads in layouts
(e.g. `.gitattributes`); `.fossil-settings/` payloads in layouts are inert by nature (fossil
reads them only at a checkout root) and need no change.
- fauxlink background: target encoded in the filename (`+` for `/`, url-style escapes), no
filesystem support required, application-driven resolution - see
`src/bootsupport/modules/fauxlink-0.1.1.tm` doctools header.
- Verification tie-in: the dual-VCS sync checks in `.fossil-settings/AGENTS.md` double as a
regression detector for this class - after this goal, the "git-tracked invisible to fossil"
comparison should show no template-payload entries.
Loading…
Cancel
Save