GitHub retires the macos-14 runner image on 2026-11-02, and its brownouts
already cancel our macos-14 jobs. Move every macos-14 runner reference to
macos-15 (arm64): the release darwin-arm64 build, the unix-macos14 and
tsan-mac test legs, the smoke-mac PR leg (its two matrix.os guards move in
lockstep), the release smoke, both soak legs, the memwaste event lane, the
bug-repro board, the cache warmer and the all-platform smoke. Lane names
stay unchanged: select-lanes.sh, ci-ok and the lane contracts key on them,
never on the runner label.
Two broad legs already ran on macos-15 (_test.yml BROAD_UNIX, _smoke.yml
BROAD_UNIX) and smoke.yml listed both images, so the moved legs would have
duplicated them. Those duplicates are dropped; PR CI keeps exactly the
same legs.
Without an explicit minimum, clang stamps the binary with the SDK's
default (LC_BUILD_VERSION minos), so a release built on macos-15 would
refuse to start on macOS 14. Makefile.cbm now folds
-mmacosx-version-min=14.0 into CC/CXX next to ARCHFLAGS for Apple targets
(detected from the compiler's target, not the host), so it reaches every
compile and link, vendored objects included, on every build path.
Proof on macOS 26.6 / SDK 26.5: the production binary built from
origin/main reports minos 26.0; with this change it reports minos 14.0,
with zero warnings under -Werror, no weak imports and an identical
undefined-symbol set. The test runner (direct make, no env.sh) is minos
14.0 too; platform and daemon_ipc suites pass.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The memory-core linter is a ratchet: raw malloc/calloc/realloc/free/strdup
sites may not grow, because allocations belong on one accounted path
(src/foundation/mem_core.h) rather than spread across hundreds of call
sites. This branch's own work had added 35 raw sites across nine files --
the parallel k8s prep, the configlink key matching, the worker pool's
counted path, the Go shared stdlib registry, the spill namespace copy,
the route service array and the namespace-name arrays.
All of them now use cbm_alloc/cbm_calloc/cbm_realloc/cbm_free/
cbm_mem_strdup. One was a latent bug rather than a style point:
pass_k8s freed a buffer that k8s_read_file had allocated with raw malloc,
so converting only the free would have mismatched the allocator -- the
read function and every free of its result are converted together.
The Windows free-space path widens its argument through the memory core
instead of cbm_utf8_to_wide(), which allocates with raw malloc.
Two files improved enough to tighten their baselines, which the ratchet
requires so the gain cannot be quietly given back: sqlite_writer.c 92->74
(the index-cell arena), pass_k8s.c 13->6 and registry.c 19->17.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Answers a question the existing tooling could not: of everything we
allocate, how much is never used?
Lanes, all driven by scripts/memwaste.sh:
event every allocation observed through the memory core — size, age,
churn, and whether the bytes were ever written
access SanitizerCoverage load/store instrumentation, so "allocated and
never touched" is measured rather than inferred
scaling k vs 2k replicas through clang profile counters, to catch
super-linear work per file
padding allocator slack per size class
ratchet the numbers recorded per venue so a regression is visible
The event lane needed a foreign-free counter: on Windows the override sees
frees for blocks it never allocated (they come from the CRT), and counting
those as untracked tripped the soundness gate. mem_events now separates
them (14 daemon / 5 worker on the Go corpus).
Two portability fixes the lanes surfaced, both real:
- the enable decision must not be cached before `environ` exists. Under
-fsanitize-coverage the UBSan preinit runs dlsym -> malloc -> our
wrapper before glibc sets up environ, so the lane silently disabled
itself and produced no dump at all on Linux.
- the access map needs 48 bits: arm64 Linux hands out addresses above
2^47, which a 47-bit map dropped on the floor.
mem_profile is retired into this: it measured a strict subset and had its
own sampling story.
Local-only by decision — not a PR-CI gate. _memwaste.yml runs the lanes on
all three platforms report-only, and pr.yml/release.yml carry the wiring.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>