Browse Source

buildsuite artifact identity fields default from punkproject.toml (punkshell 0.37.0)

G-117/G-123 lineage: project, project_url and packager in emitted
punkbin-artifact.toml records (runtime family + library tier, both
suites) now default from the enclosing project's punkproject.toml
[project] table via a bounded walk-up from the build root
(common.punkprojectField) - a copied suite reads its own project's
manifest. Chains: project: -Dproject > manifest name > "punkshell"
(was hardcoded); project_url: -Dprojecturl > manifest url >
"unrecorded"; packager: -Dpackager > PUNKBIN_PACKAGER > manifest
packager > git identity > fossil user default (new fossil-only
fallback) > "unrecorded". Verified end-to-end: suite_tcl90
kit-family + library emissions and the suite_tcl86 library emission
carry the manifest values, -D overrides win, family_check green.

Root punkproject.toml gains an active url; dev project.new's seeded
manifest now carries commented url/packager placeholders documenting
the consumption site (punk::mix::commandset::project 0.4.1).

Assisted-by: harness=opencode; primary-model=huggingface/moonshotai/Kimi-K3; api-location=unknown
master
Julian Noble 2 days ago
parent
commit
a71d7bd377
  1. 16
      CHANGELOG.md
  2. 7
      punkproject.toml
  3. 6
      src/buildsuites/suite_tcl86/README.md
  4. 26
      src/buildsuites/suite_tcl86/build86.zig
  5. 49
      src/buildsuites/suite_tcl86/build_common.zig
  6. 15
      src/buildsuites/suite_tcl90/README.md
  7. 30
      src/buildsuites/suite_tcl90/build905.zig
  8. 47
      src/buildsuites/suite_tcl90/build_common.zig
  9. 9
      src/modules/punk/mix/commandset/project-999999.0a1.0.tm
  10. 3
      src/modules/punk/mix/commandset/project-buildversion.txt

16
CHANGELOG.md

@ -5,6 +5,22 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.37.0] - 2026-07-31
- Buildsuite artifact identity fields (`project`, `project_url`, `packager`)
now default from the enclosing project's `punkproject.toml` `[project]`
table (G-117/G-123 lineage): both suite recipes locate the nearest
manifest by a bounded walk-up from the build root, so a copied suite
reads its own project's file. Chains - `project`: `-Dproject` > manifest
`name` > "punkshell" (previously hardcoded); `project_url`:
`-Dprojecturl` > manifest `url` > "unrecorded"; `packager`: `-Dpackager`
> `PUNKBIN_PACKAGER` env > manifest `packager` > git identity > `fossil
user default` (new fossil-only fallback) > "unrecorded". Verified
end-to-end in both suites' emissions. The root `punkproject.toml` gained
an active `url`, and `dev project.new`'s seeded manifest now carries
commented `url`/`packager` placeholders documenting where those fields
are consumed (punk::mix::commandset::project 0.4.1).
## [0.36.0] - 2026-07-31
- `punk-runtime` fetch reports the artifact's build-origin class after

7
punkproject.toml

@ -1,4 +1,9 @@
[project]
name = "punkshell"
version = "0.36.0"
version = "0.37.0"
license = "BSD-2-Clause"
url = "https://www.gitea1.intx.com.au/jn/punkshell"
#packager: declared identity for published artifacts (declarative, not proof -
#signing is the verification layer). Resolution: -Dpackager > PUNKBIN_PACKAGER
#env > this field > git identity > fossil user default > unrecorded.
#packager = "Julian Noble <julian@precisium.com.au>"

6
src/buildsuites/suite_tcl86/README.md

@ -74,7 +74,11 @@ ACTIVE - see `goals/G-100-suite-tcl86-tk-tcllib.md` for the progress ledger.
Forked from suite_tcl90's step (see its README "Library artifacts (G-138)"
for the full mechanism: generation-tagged names, embed-then-hash, punkzip
determinism - byte-identical re-emission verified here too, publish
discipline for the non-bit-reproducible dlls, md5c disposition). Runs under
discipline for the non-bit-reproducible dlls, md5c disposition). The artifact
identity fields (`project`, `project_url`, `packager`) resolve per the chains
documented in suite_tcl90's README ("Runtime kit family" section) - this
suite's emission consumes the same `punkproject.toml` defaults and
`-Dproject`/`-Dprojecturl`/`-Dpackager` options. Runs under
the INSTALLED STATIC SHELL (this suite has no kit family): sha1/tcllibc
resolve from `<prefix>/lib`, so emission still proves the built shell
executes the very packages being published. Revision `-Dlibrev=N`

26
src/buildsuites/suite_tcl86/build86.zig

@ -879,16 +879,23 @@ pub fn build(b: *std.Build) !void {
});
//G-117 schema identity options (introduced here by the library step -
//this suite emits no runtime-family artifacts): origin = canonical
//artifact repo the artifacts are BUILT FOR; packager = declared
//identity, resolution chain -Dpackager > env PUNKBIN_PACKAGER >
//building checkout's git identity > "unrecorded".
//artifact repo the artifacts are BUILT FOR; project/projecturl/packager
//default from the enclosing project's punkproject.toml [project] table
//(common.punkprojectField: bounded walk-up from the build root - covers
//the suite source dir and the staged _build recipe dir alike; a copied
//suite reads ITS project's manifest). packager = declared identity,
//resolution chain -Dpackager > env PUNKBIN_PACKAGER > punkproject.toml
//[project] packager > building checkout's git identity > 'fossil user
//default' > "unrecorded".
const originurl = b.option([]const u8, "originurl", "canonical artifact repo url the library artifacts are built FOR ('origin' field; default punkbin)") orelse "https://www.gitea1.intx.com.au/jn/punkbin";
const projecturl = b.option([]const u8, "projecturl", "project url recorded in the artifact records ('project_url' field; default unrecorded)") orelse "unrecorded";
const project = b.option([]const u8, "project", "project name recorded in the artifact records ('project' field; default: punkproject.toml [project] name, else punkshell)") orelse common.punkprojectField(b, "name") orelse "punkshell";
const projecturl = b.option([]const u8, "projecturl", "project url recorded in the artifact records ('project_url' field; default: punkproject.toml [project] url, else unrecorded)") orelse common.punkprojectField(b, "url") orelse "unrecorded";
const packager = blk: {
if (b.option([]const u8, "packager", "declared packager identity for the artifact records (-Dpackager > env PUNKBIN_PACKAGER > git identity > unrecorded)")) |p| break :blk p;
if (b.option([]const u8, "packager", "declared packager identity for the artifact records (-Dpackager > env PUNKBIN_PACKAGER > punkproject.toml [project] packager > git identity > fossil user default > unrecorded)")) |p| break :blk p;
if (b.graph.environ_map.get("PUNKBIN_PACKAGER")) |p| {
if (p.len != 0) break :blk p;
}
if (common.punkprojectField(b, "packager")) |p| break :blk p;
//out_code is only meaningful on some runAllowFail paths - judge
//success by usable trimmed output, not the code
var gc: u8 = undefined;
@ -904,6 +911,13 @@ pub fn build(b: *std.Build) !void {
break :blk nm;
}
}
//fossil leg: the fossil-only project fallback - bare default user
//(fossil has no email convention); declarative like the rest.
const fuser: ?[]const u8 = b.runAllowFail(&.{ "fossil", "user", "default" }, &gc, .ignore) catch null;
if (fuser != null) {
const fu = std.mem.trim(u8, fuser.?, " \t\r\n");
if (fu.len != 0) break :blk b.dupe(fu);
}
break :blk "unrecorded";
};
//source checkout provenance: tcllib manifest.uuid (suite.tcl's fossil
@ -929,7 +943,7 @@ pub fn build(b: *std.Build) !void {
lib_emit.addFileArg(b.path("tools/library_artifacts.tcl"));
lib_emit.addArgs(&.{ "-outdir", libart_outdir, "-rev", b.fmt("{d}", .{librev}), "-generation", "tcl8", "-target", "win32-x86_64" });
lib_emit.addArgs(&.{ "-tclpatch", tcl_h_patchlevel, "-suite", "suite_tcl86", "-zig", builtin.zig_version_string, "-optimize", @tagName(optimize) });
lib_emit.addArgs(&.{ "-origin", originurl, "-packager", packager, "-project", "punkshell", "-projecturl", projecturl });
lib_emit.addArgs(&.{ "-origin", originurl, "-packager", packager, "-project", project, "-projecturl", projecturl });
lib_emit.addArgs(&.{ "-provenance", b.fmt("tcllib {s} critcl {s}", .{ uuid_tcllib, critcl_uuid }) });
lib_emit.addArg("-punkzip");
lib_emit.addArtifactArg(punkzip_exe);

49
src/buildsuites/suite_tcl86/build_common.zig

@ -84,6 +84,55 @@ pub fn manifestUuid(b: *std.Build, tree_from_root: []const u8) []const u8 {
return std.mem.trim(u8, data, " \t\r\n");
}
//punkproject.toml [project] field reader (G-117/G-123 lineage; mirror of the
//suite_tcl90 helper - shared scaffolding stays duplicated per this suite's
//fork note): bounded walk-up from the build root for the NEAREST
//punkproject.toml - covers the suite source dir (2 up to the project root) and
//the staged _build/<suite>/build recipe dir (5 up) alike; _build itself never
//carries one. The nearest file is the project boundary: a missing key in it
//means undeclared (null), never a walk into an outer project's identity.
//Line-based [project]-section scan for a quoted key - the same tolerant
//line-based consumer style as the punkbin record readers ('#' comment lines
//and unknown keys ignored).
pub fn punkprojectField(b: *std.Build, key: []const u8) ?[]const u8 {
var rel: []const u8 = "punkproject.toml";
var depth: usize = 0;
while (depth < 10) : (depth += 1) {
const abs = b.pathFromRoot(rel);
if (std.Io.Dir.cwd().readFileAlloc(b.graph.io, abs, b.allocator, .limited(64 * 1024)) catch null) |data| {
return punkprojectParse(b, data, key);
}
rel = b.fmt("../{s}", .{rel});
}
return null;
}
//line-based [project]-section scan for a quoted key: section headers may carry
//a trailing comment; values are simple quoted strings (urls, names - no escape
//processing); first match wins.
fn punkprojectParse(b: *std.Build, data: []const u8, key: []const u8) ?[]const u8 {
var in_project = false;
var lines = std.mem.splitScalar(u8, data, '\n');
while (lines.next()) |line| {
const t = std.mem.trim(u8, line, " \t\r");
if (t.len == 0 or t[0] == '#') continue;
if (t[0] == '[') {
in_project = std.mem.startsWith(u8, t, "[project]") and
(t.len == "[project]".len or t["[project]".len] == ' ' or t["[project]".len] == '\t' or t["[project]".len] == '#');
continue;
}
if (!in_project) continue;
if (!std.mem.startsWith(u8, t, key)) continue;
const after_key = t[key.len..];
if (after_key.len == 0 or (after_key[0] != ' ' and after_key[0] != '\t' and after_key[0] != '=')) continue;
const q1 = std.mem.indexOfScalar(u8, after_key, '"') orelse continue;
const rest = after_key[q1 + 1 ..];
const q2 = std.mem.indexOfScalar(u8, rest, '"') orelse continue;
return b.dupe(rest[0..q2]);
}
return null;
}
//package version from a TEA tree's configure.ac 'AC_INIT([name],[version])'
//line (thread, tclvfs, ...). Version-derived artifact names must follow the
//checkout - hardcoded versions stamp mismatched sources (suite_tcl90's G-107

15
src/buildsuites/suite_tcl90/README.md

@ -162,6 +162,21 @@ run re-proves the family runtime executes real tooling self-contained.
Publication to the real punkbin repo remains a deliberate user step, deferred
per the goal notes until the family shape is accepted.
Artifact identity fields (`project`, `project_url`, `packager` - G-117 lineage)
resolve at recipe configure time, for BOTH the family and library emissions.
The chains (`punkproject.toml` = the enclosing project's manifest, found by a
bounded walk-up from the build root - the suite source dir and the staged
`_build` recipe dir both find the project root's file, and a copied suite reads
ITS project's manifest):
- `project`: `-Dproject` > punkproject.toml `[project] name` > `"punkshell"`
- `project_url`: `-Dprojecturl` > punkproject.toml `[project] url` > `"unrecorded"`
- `packager`: `-Dpackager` > `PUNKBIN_PACKAGER` env > punkproject.toml
`[project] packager` > building checkout's git identity > `fossil user
default` (bare username - the fossil-only fallback) > `"unrecorded"`
suite.tcl passes the options through `-zigargs` (e.g `-zigargs {-Dprojecturl=https://...}`).
Consumers: working-name runtimes are copied into `bin/runtime/win32-x86_64/`
and referenced by `src/runtime/mapvfs.config` (punk -> punk9wintk905.vfs as
`punk9_beta`, punk-bi -> punk9win_for_tkruntime.vfs as `punk9bi_beta`, per the

30
src/buildsuites/suite_tcl90/build905.zig

@ -2434,16 +2434,23 @@ pub fn build(b: *std.Build) !void {
const familyrev = b.option(u32, "familyrev", "assembly revision N for the -r<N> family artifact names (kit-family-artifacts; default 2 - r1 published 2026-07-22)") orelse 2;
//G-117 schema identity options (v2 as of G-123). origin = canonical artifact repo the
//artifacts are BUILT FOR (not necessarily where they end up hosted -
//mirrors preserve it). packager = declared identity, resolution chain
//-Dpackager > env PUNKBIN_PACKAGER > building checkout's git identity >
//"unrecorded" (declarative, not proof - signing is the verification layer).
//mirrors preserve it). project/projecturl/packager default from the enclosing
//project's punkproject.toml [project] table (common.punkprojectField: bounded
//walk-up from the build root - covers the suite source dir and the staged
//_build recipe dir alike; a copied suite reads ITS project's manifest).
//packager = declared identity, resolution chain -Dpackager > env
//PUNKBIN_PACKAGER > punkproject.toml [project] packager > building checkout's
//git identity > 'fossil user default' > "unrecorded" (declarative, not
//proof - signing is the verification layer).
const originurl = b.option([]const u8, "originurl", "canonical artifact repo url the family artifacts are built FOR ('origin' field; default punkbin)") orelse "https://www.gitea1.intx.com.au/jn/punkbin";
const projecturl = b.option([]const u8, "projecturl", "project url recorded in the artifact records ('project_url' field; default unrecorded)") orelse "unrecorded";
const project = b.option([]const u8, "project", "project name recorded in the artifact records ('project' field; default: punkproject.toml [project] name, else punkshell)") orelse common.punkprojectField(b, "name") orelse "punkshell";
const projecturl = b.option([]const u8, "projecturl", "project url recorded in the artifact records ('project_url' field; default: punkproject.toml [project] url, else unrecorded)") orelse common.punkprojectField(b, "url") orelse "unrecorded";
const packager = blk: {
if (b.option([]const u8, "packager", "declared packager identity for the artifact records (-Dpackager > env PUNKBIN_PACKAGER > git identity > unrecorded)")) |p| break :blk p;
if (b.option([]const u8, "packager", "declared packager identity for the artifact records (-Dpackager > env PUNKBIN_PACKAGER > punkproject.toml [project] packager > git identity > fossil user default > unrecorded)")) |p| break :blk p;
if (b.graph.environ_map.get("PUNKBIN_PACKAGER")) |p| {
if (p.len != 0) break :blk p;
}
if (common.punkprojectField(b, "packager")) |p| break :blk p;
//out_code is only meaningful on some runAllowFail paths - judge
//success by usable trimmed output, not the code
var gc: u8 = undefined;
@ -2459,6 +2466,13 @@ pub fn build(b: *std.Build) !void {
break :blk nm;
}
}
//fossil leg: the fossil-only project fallback - bare default user
//(fossil has no email convention); declarative like the rest.
const fuser: ?[]const u8 = b.runAllowFail(&.{ "fossil", "user", "default" }, &gc, .ignore) catch null;
if (fuser != null) {
const fu = std.mem.trim(u8, fuser.?, " \t\r\n");
if (fu.len != 0) break :blk b.dupe(fu);
}
break :blk "unrecorded";
};
@ -2543,7 +2557,7 @@ pub fn build(b: *std.Build) !void {
\\#packager: declared identity, not proof - signing (minisign sidecars)
\\#is the verification layer.
\\packager = "{s}"
\\project = "punkshell"
\\project = "{s}"
\\project_url = "{s}"
\\#license: summary for the distributed artifact; component license texts
\\#ride inside the attached image (tcl_library/license.terms etc).
@ -2566,7 +2580,7 @@ pub fn build(b: *std.Build) !void {
\\optimize = "{s}"
\\{s}
\\
, .{ artifact_name, fk.variant, fk.working_name, familyrev, build_id, originurl, packager, projecturl, tcl_h_patchlevel, piperepl_block, batteries, builtin.zig_version_string, @tagName(optimize), prov_lines });
, .{ artifact_name, fk.variant, fk.working_name, familyrev, build_id, originurl, packager, project, projecturl, tcl_h_patchlevel, piperepl_block, batteries, builtin.zig_version_string, @tagName(optimize), prov_lines });
}
var family_tree_base: [family_kits.len]std.Build.LazyPath = undefined;
@ -2712,7 +2726,7 @@ pub fn build(b: *std.Build) !void {
lib_emit.addFileArg(b.path("tools/library_artifacts.tcl"));
lib_emit.addArgs(&.{ "-outdir", libart_outdir, "-rev", b.fmt("{d}", .{librev}), "-generation", "tcl9", "-target", "win32-x86_64" });
lib_emit.addArgs(&.{ "-tclpatch", tcl_h_patchlevel, "-suite", "suite_tcl90", "-zig", builtin.zig_version_string, "-optimize", @tagName(optimize) });
lib_emit.addArgs(&.{ "-origin", originurl, "-packager", packager, "-project", "punkshell", "-projecturl", projecturl });
lib_emit.addArgs(&.{ "-origin", originurl, "-packager", packager, "-project", project, "-projecturl", projecturl });
lib_emit.addArgs(&.{ "-provenance", b.fmt("tcllib {s} critcl {s}", .{ uuid_tcllib, critcl_uuid }) });
lib_emit.addArg("-punkzip");
lib_emit.addArtifactArg(punkzip_exe);

47
src/buildsuites/suite_tcl90/build_common.zig

@ -63,6 +63,53 @@ pub fn manifestUuid(b: *std.Build, tree_from_root: []const u8) []const u8 {
return std.mem.trim(u8, data, " \t\r\n");
}
//punkproject.toml [project] field reader (G-117/G-123 lineage): bounded walk-up
//from the build root for the NEAREST punkproject.toml - covers the suite source
//dir (2 up to the project root) and the staged _build/<suite>/build recipe dir
//(5 up) alike; _build itself never carries one. The nearest file is the project
//boundary: a missing key in it means undeclared (null), never a walk into an
//outer project's identity. Line-based [project]-section scan for a quoted key -
//the same tolerant line-based consumer style as the punkbin record readers
//('#' comment lines and unknown keys ignored).
pub fn punkprojectField(b: *std.Build, key: []const u8) ?[]const u8 {
var rel: []const u8 = "punkproject.toml";
var depth: usize = 0;
while (depth < 10) : (depth += 1) {
const abs = b.pathFromRoot(rel);
if (std.Io.Dir.cwd().readFileAlloc(b.graph.io, abs, b.allocator, .limited(64 * 1024)) catch null) |data| {
return punkprojectParse(b, data, key);
}
rel = b.fmt("../{s}", .{rel});
}
return null;
}
//line-based [project]-section scan for a quoted key: section headers may carry
//a trailing comment; values are simple quoted strings (urls, names - no escape
//processing); first match wins.
fn punkprojectParse(b: *std.Build, data: []const u8, key: []const u8) ?[]const u8 {
var in_project = false;
var lines = std.mem.splitScalar(u8, data, '\n');
while (lines.next()) |line| {
const t = std.mem.trim(u8, line, " \t\r");
if (t.len == 0 or t[0] == '#') continue;
if (t[0] == '[') {
in_project = std.mem.startsWith(u8, t, "[project]") and
(t.len == "[project]".len or t["[project]".len] == ' ' or t["[project]".len] == '\t' or t["[project]".len] == '#');
continue;
}
if (!in_project) continue;
if (!std.mem.startsWith(u8, t, key)) continue;
const after_key = t[key.len..];
if (after_key.len == 0 or (after_key[0] != ' ' and after_key[0] != '\t' and after_key[0] != '=')) continue;
const q1 = std.mem.indexOfScalar(u8, after_key, '"') orelse continue;
const rest = after_key[q1 + 1 ..];
const q2 = std.mem.indexOfScalar(u8, rest, '"') orelse continue;
return b.dupe(rest[0..q2]);
}
return null;
}
//package version from a TEA tree's configure.ac 'AC_INIT([name],[version])'
//line (thread, tclvfs, ...). G-107: version-derived artifact names must follow
//the checkout - hardcoded versions stamp mismatched sources (the thread recipe

9
src/modules/punk/mix/commandset/project-999999.0a1.0.tm

@ -529,6 +529,15 @@ namespace eval punk::mix::commandset::project {
puts $fd "\[project\]"
puts $fd "name = \"$projectname\""
puts $fd "version = \"0.1.0\""
puts $fd "#url: project home/repository url. Consumed by buildsuite artifact"
puts $fd "#emission (punkbin-artifact.toml 'project_url' field; -Dprojecturl"
puts $fd "#overrides). Uncomment and set before publishing artifacts."
puts $fd "#url = \"https://example.com/$projectname\""
puts $fd "#packager: declared identity for published artifacts (declarative, not"
puts $fd "#proof - signing is the verification layer). Resolution: -Dpackager >"
puts $fd "#PUNKBIN_PACKAGER env > this field > git identity > fossil user default >"
puts $fd "#unrecorded."
puts $fd "#packager = \"Your Name <you@example.com>\""
close $fd
puts stdout "seeded punkproject.toml (\[project\] name '$projectname' version 0.1.0)"
}

3
src/modules/punk/mix/commandset/project-buildversion.txt

@ -1,6 +1,7 @@
0.4.0
0.4.1
#First line must be a semantic version number
#all other lines are ignored.
#0.4.1 - project.new's seeded punkproject.toml now carries commented-out url/packager placeholder fields with '#' documentation lines naming their consumption site (buildsuite artifact emission: punkbin-artifact.toml project_url/packager fields, G-117/G-123 lineage) - generated projects learn where those fields are used; default behaviour unchanged (both stay undeclared until uncommented)
#0.4.0 - G-087 stage 4: project.new copies the layout from punk::mix::commandset::layout::lib::layout_materialize's effective content folder - overlay chains (custom-over-vendor merging, .anti deletion markers, name@base derived layouts) are composed into a disposable staging dir (deleted after use); single-folder chains with no .anti markers keep the direct store-folder path (no behaviour change for punk.project / module-carried layouts). Template scanning uses the new path-based layoutfolder_scan_for_template_files against the effective folder. The src/PROJECT_LAYOUTS_* install exclusion was retired with the marker folder (G-087 stage 5) and the misnamed antipaths variable folded into exclude_paths.
#0.4.0 - G-087 stage 5: project.new seeds generated-project version metadata post-materialization (all only-if-missing, never from payload): punkproject.toml ([project] name + version 0.1.0), CHANGELOG.md with a matching ## [0.1.0] header (keeps the injected make.tcl projectversion advisory quiet), and punkorigin.toml - an interim generation-provenance baseline (utc, layout name + resolved store target, punk::mix::templates version, punkshell version when derivable from the generating shell's checkout via punkboot::utils, origin projectroot path hint) for the G-027 pull-update design to adopt.
#0.3.1 - fix: project.new validates module names (projectname-derived default and explicit -modules entries, modulename-version forms accepted) before any folder/fossil-repo creation - a dashed project name (e.g infra-man) previously failed late inside module.new after the project tree and fossil repo already existed

Loading…
Cancel
Save