Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,52 @@ versioning; breaking changes to it bump the major version.

## [Unreleased]

## [1.7.4] - 2026-08-07

### Changed

- Bumped `pozeiden` v0.3.0 → v0.4.1. Mermaid diagrams pick up the O(V+E)
layout rewrite (graph caps raised 10× to 10 000 nodes / 20 000 edges), the
front-matter and deep-layout rendering fixes, 0.4.1's direction-override
subgraph fix (same-layer nodes no longer swap positions), and a default
font stack that now ends in `Liberation Sans, DejaVu Sans, sans-serif` —
so Typst resolves real fonts without the per-call override PolicyPress
applies. Diagrams with mermaid `accTitle:`/`accDescr:` directives now
carry SVG `<title>`/`<desc>` accessibility metadata. Golden Typst
baselines are byte-identical (mermaid SVG is golden-tested upstream, not
re-pinned here).

- Bumped `zigmark` v0.11.0 → v0.11.1, whose lazy `pozeiden` pin jumps
v0.2.0 → v0.4.1. That stale 0.2.0 pin sat in PolicyPress's lock alongside
the direct pozeiden dependency and predated both 0.3.0 security fixes
(GHSA-p2c5-qmq5-3r4f SVG/XSS injection, GHSA-rg4m-w3p2-gf3p out-of-bounds
writes); the lock now carries a single pozeiden entry at v0.4.1.

- Bad praxis join files are now diagnosed through the standard log (scoped
`praxis_join`, warn level) instead of raw stderr prints. The message text is
unchanged, but the diagnostics now respect `--quiet` and no longer corrupt
`--json-log` output.

### Fixed

- `update-zon` can no longer destroy `build.zig.zon2json-lock`. zig2nix's
`zon2lock` truncates its destination before it starts fetching and stages
its `zig fetch` runs in a `/tmp` scratch dir — with the dev shell's
project-relative `ZIG_GLOBAL_CACHE_DIR`, the repacked dependency tarballs
were written under that scratch dir but read back from the repo cache, so
every dependency bump crashed with `FileNotFound` and left a truncated
lock behind. The dev shell now exports an absolute cache path, and
`update-zon` generates into a temp file, validates the JSON, and only then
moves it into place (pozeiden's guard), so a crash cannot eat the lock.

- `zig build test` no longer ends every run with a misleading
`failed command: … --listen=-` dump. The suite deliberately drives warning
paths, those expected warnings landed on stderr, and the zig 0.16 build
runner re-prints any stderr captured from a *passing* test binary under
that failure-looking trailer. Tests that intend to warn now silence
warn-level logs for their duration (`std.testing.log_level`), so a clean
run prints nothing and real diagnostics stand out.

## [1.7.3] - 2026-07-31

### Fixed
Expand Down
10 changes: 5 additions & 5 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// In a future version of Zig it will be used for package deduplication.

// When bumping this, also update extra.version in config.toml.
.version = "1.7.3",
.version = "1.7.4",

// Together with name, this represents a globally unique package
// identifier. This field is generated by the Zig toolchain when the
Expand Down Expand Up @@ -42,8 +42,8 @@
// Switch to git+https URLs with hashes before publishing to FlakeHub.
// .zetta = .{ .path = "../zetta" },
.zigmark = .{
.url = "git+https://github.com/sc2in/zigmark?ref=v0.11.0#63f68d4dd759836e35900b75a140a35ed7ab2223",
.hash = "zigmark-0.11.0-cwOiyGavCwD_RfjSI5UU-xGwQzgKbfnFv3Gjd454Ypqx",
.url = "https://github.com/sc2in/zigmark/archive/refs/tags/v0.11.1.tar.gz",
.hash = "zigmark-0.11.1-cwOiyF2vCwB1wx2a1OsvpOb8f9Rfmjw64IP73I7Pj07D",
},
.tomlz = .{
.url = "git+https://github.com/sc2in/tomlz.git#ecd64e239768b573ec58286d0db1842991433e99",
Expand All @@ -62,8 +62,8 @@
.hash = "clap-0.11.0-oBajB-jlAQA8x4XSScN1d48Q83iYl-LDU63htNyXbXBe",
},
.pozeiden = .{
.url = "git+https://github.com/sc2in/pozeiden?ref=v0.3.0#a29e792fd7b5c8fa9f8d939536552d382c94c871",
.hash = "pozeiden-0.3.0-NAqiXXIpCgC2prHxCXJe59cXjbV7f8KcrHb9nstNkhOy",
.url = "https://github.com/sc2in/pozeiden/archive/refs/tags/v0.4.1.tar.gz",
.hash = "pozeiden-0.4.1-NAqiXUkfCwBbeMXQus5ba8zxE3oEyZtnD_OtL_35qKi8",
},
},
.paths = .{
Expand Down
19 changes: 7 additions & 12 deletions build.zig.zon2json-lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"zigmark-0.11.0-cwOiyGavCwD_RfjSI5UU-xGwQzgKbfnFv3Gjd454Ypqx": {
"zigmark-0.11.1-cwOiyF2vCwB1wx2a1OsvpOb8f9Rfmjw64IP73I7Pj07D": {
"name": "zigmark",
"url": "git+https://github.com/sc2in/zigmark?ref=v0.11.0#63f68d4dd759836e35900b75a140a35ed7ab2223",
"hash": "sha256-syIAWFl/bpBF0UnmjZ/h/KRH/2eJuU/hH+RxHMWzi7o="
"url": "https://github.com/sc2in/zigmark/archive/refs/tags/v0.11.1.tar.gz",
"hash": "sha256-Vcd7gk9YfqYS7aQNLTCAQFCPEfpV51qNvfmjiYyn5Zs="
},
"tomlz-0.3.0-H2E6w3VKAgCSZQFKG-VugLEn3cjOWshGqwGzAVABNm--": {
"name": "tomlz",
Expand Down Expand Up @@ -34,14 +34,14 @@
"url": "git+https://github.com/github/cmark-gfm.git#587a12bb54d95ac37241377e6ddc93ea0e45439b",
"hash": "sha256-egCWzw09e5daT8cnlSORC3gep9soiqonVC4GQ/s3voM="
},
"pozeiden-0.2.0-NAqiXaypCgBCkAMB3WZpGLPnwV5XTsUvOf3KUY9aAN0p": {
"pozeiden-0.4.1-NAqiXUkfCwBbeMXQus5ba8zxE3oEyZtnD_OtL_35qKi8": {
"name": "pozeiden",
"url": "https://github.com/sc2in/pozeiden/archive/refs/tags/v0.2.0.tar.gz",
"hash": "sha256-9j8xkNndJX+l20+/qlfK2aLFCfPQsX1xoTDQ+V2jXUg="
"url": "https://github.com/sc2in/pozeiden/archive/refs/tags/v0.4.1.tar.gz",
"hash": "sha256-JRF9suGxbgmYBSkkfgJjiQB57fi5itR6B2RfNNbYMtw="
},
"mvzr-0.3.9-ZSOky8FzAQBQ9-GkQnaLjOZZHxrioD8NwY-QyZT6oAyR": {
"name": "mvzr",
"url": "git+https://github.com/mnemnion/mvzr?ref=v0.3.10#dd0e1bd2d6b10f9650317b30baef7ab7bc9dd9ec",
"url": "git+https://github.com/mnemnion/mvzr?ref=v0.3.9#dd0e1bd2d6b10f9650317b30baef7ab7bc9dd9ec",
"hash": "sha256-hUGdJPjC1PAm6zWL9eAE7A+wAfzf6Q7uSutT2Xe7EMU="
},
"zig_yaml-0.3.2-C1161q3CAgCEpqWl_MKjkQpPZmos4DHBJewpb58XMcAL": {
Expand All @@ -63,10 +63,5 @@
"name": "clap",
"url": "git+https://github.com/Hejsil/zig-clap#1d3d273524e3c180f015ff1e93c83075e4634e2c",
"hash": "sha256-Ytdm6tGAO+2V5jNLn7oB0r4DnmhoL6PIDQ0ihxzsLts="
},
"pozeiden-0.3.0-NAqiXXIpCgC2prHxCXJe59cXjbV7f8KcrHb9nstNkhOy": {
"name": "pozeiden",
"url": "git+https://github.com/sc2in/pozeiden?ref=v0.3.0#a29e792fd7b5c8fa9f8d939536552d382c94c871",
"hash": "sha256-e84o2po+IH2matp/iAhJBjtGOtqBmLPDsbiRggwWg0c="
}
}
2 changes: 1 addition & 1 deletion config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ include_content = true

[extra]
homepage_style = "marketing"
version = "1.7.3"
version = "1.7.4"
# release = "https://api.github.com/repos/getzola/zola/releases/latest"
favicon = "https://www.getzola.org/favicon.ico"
easydocs_logo_always_clickable = true
Expand Down
35 changes: 32 additions & 3 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -1016,15 +1016,38 @@
# fills the disk (see zigGuarded above, which now blocks it):
# zig fetch --save=<name> "git+https://…?ref=vX.Y.Z#<commit>"
# then run `update-zon`.
cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
# Re-anchor the cache for standalone robustness: zon2lock
# stages `zig fetch` runs in a /tmp scratch dir, so a
# relative ZIG_GLOBAL_CACHE_DIR (e.g. inherited from a stale
# shell) writes the repacked dependency tarballs there while
# the lock builder reads them back from this repo —
# FileNotFound at zon2lock's repack step (#197). An absolute
# path keeps both sides on <repo>/.zig-cache, which also
# lets zon2lock reuse what `zig build --fetch` just fetched.
export ZIG_GLOBAL_CACHE_DIR="$PWD/.zig-cache"
echo "Fetching the full dependency graph…"
zig build --fetch
echo "Regenerating build.zig.zon2json-lock…"
# zon2lock truncates its destination before it starts
# fetching, so writing straight to the real lock leaves a
# 0-byte or partial file behind on a crash or ^C. Generate
# into a temp file, validate, then move into place
# (pozeiden's guard).
tmp="$(mktemp .build.zig.zon2json-lock.XXXXXX)"
trap 'rm -f "$tmp"' EXIT
# zig2nix has no plain `zig2nix` binary on PATH; its CLI is
# the flake app, pinned here so this never hits the network.
${zig2nix.apps.${system}.default.program} zon2lock
${zig2nix.apps.${system}.default.program} zon2lock build.zig.zon "$tmp"
if ! ${pkgs.jq}/bin/jq -e 'type == "object"' "$tmp" >/dev/null 2>&1; then
echo "error: generated lock is empty or invalid JSON; keeping existing build.zig.zon2json-lock" >&2
exit 1
fi
# zon2lock omits the trailing newline that the end-of-file-fixer
# pre-commit hook requires; add exactly one.
[ -n "$(tail -c1 build.zig.zon2json-lock)" ] && printf '\n' >> build.zig.zon2json-lock
[ -n "$(tail -c1 "$tmp")" ] && printf '\n' >> "$tmp"
mv -f "$tmp" build.zig.zon2json-lock
trap - EXIT
# zon2lock unpacks every dependency into ./zig-pkg/<hash> and
# means to delete each one when it is done reading it, but its
# cleanup resolves the path against the dependency dir instead
Expand All @@ -1041,7 +1064,13 @@

shellHook = config.pre-commit.installationScript + ''
export TYPST_FONT_PATHS="${typstFonts}/share/fonts"
export ZIG_GLOBAL_CACHE_DIR=.zig-cache
# Absolute, not relative: tools that chdir before invoking zig
# resolve a relative value against their own cwd. zig2nix's
# zon2lock stages `zig fetch` runs in a /tmp scratch dir, so a
# relative ".zig-cache" made it write each repacked dependency
# tarball under /tmp/zig2nix_*/.zig-cache while reading it back
# from <repo>/.zig-cache — FileNotFound on every bump (#197).
export ZIG_GLOBAL_CACHE_DIR="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.zig-cache"

# A leftover zig-pkg/.tmp-* is the signature of a fetch that was
# interrupted or that recursed into itself. One sat here unnoticed
Expand Down
18 changes: 12 additions & 6 deletions src/praxis_join.zig
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const tst = std.testing;

// Scoped log rather than std.debug.print: the CLI output is identical (the
// custom logFns print the bare message), but the diagnostics now respect
// --quiet / --json-log, and the tests that deliberately feed this loader bad
// files can silence the expected warnings via `std.testing.log_level`.
const joinlog = std.log.scoped(.praxis_join);

/// Schema string every join file must carry verbatim. A mismatch is a hard
/// error: silently accepting an unknown shape would let a future or foreign
/// format pass as coverage data.
Expand Down Expand Up @@ -67,7 +73,7 @@ pub const PraxisJoin = struct {
pub fn load(io: std.Io, alloc: Allocator, path: []const u8) !PraxisJoin {
var f = std.Io.Dir.cwd().openFile(io, path, .{ .mode = .read_only }) catch |e| blk: {
if (e == error.FileNotFound) break :blk std.Io.Dir.openFileAbsolute(io, path, .{ .mode = .read_only }) catch |e2| {
std.debug.print("praxis join file not found: '{s}'\n", .{path});
joinlog.warn("praxis join file not found: '{s}'", .{path});
return e2;
} else return e;
};
Expand All @@ -82,27 +88,27 @@ pub const PraxisJoin = struct {
const content = try fr.interface.allocRemaining(a, .limited(4_000_000));

const root = std.json.parseFromSliceLeaky(std.json.Value, a, content, .{}) catch |e| {
std.debug.print("praxis join file '{s}' is not valid JSON: {s}\n", .{ path, @errorName(e) });
joinlog.warn("praxis join file '{s}' is not valid JSON: {s}", .{ path, @errorName(e) });
return Error.MalformedJoinFile;
};
if (root != .object) {
std.debug.print("praxis join file '{s}' must be a JSON object\n", .{path});
joinlog.warn("praxis join file '{s}' must be a JSON object", .{path});
return Error.MalformedJoinFile;
}
const obj = root.object;

// Schema gate first: the one hard, distinct error callers branch on. An
// unknown or missing schema must never be treated as coverage data.
const schema_v = obj.get("schema") orelse {
std.debug.print("praxis join file '{s}' has no 'schema' field (expected \"{s}\")\n", .{ path, schema_id });
joinlog.warn("praxis join file '{s}' has no 'schema' field (expected \"{s}\")", .{ path, schema_id });
return Error.MissingSchema;
};
if (schema_v != .string) {
std.debug.print("praxis join file '{s}' 'schema' must be a string (expected \"{s}\")\n", .{ path, schema_id });
joinlog.warn("praxis join file '{s}' 'schema' must be a string (expected \"{s}\")", .{ path, schema_id });
return Error.MissingSchema;
}
if (!std.mem.eql(u8, schema_v.string, schema_id)) {
std.debug.print("praxis join file '{s}' has schema \"{s}\"; expected \"{s}\"\n", .{ path, schema_v.string, schema_id });
joinlog.warn("praxis join file '{s}' has schema \"{s}\"; expected \"{s}\"", .{ path, schema_v.string, schema_id });
return Error.SchemaMismatch;
}

Expand Down
Loading
Loading