Commit Graph
1765 Commits
Author SHA1 Message Date
Martin Vogel 718037f02d wip: per-connection census at kilobyte resolution
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 18:10:42 +02:00
Martin Vogel bee8658c4d wip: windows region census to attribute committed memory by pool
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 17:59:33 +02:00
Martin Vogel 249e316efc wip: collect at all per-job thread exits
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 17:09:31 +02:00
Martin Vogel 3d0d056464 wip: collect allocator pages at connection-worker exit
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 16:59:52 +02:00
Martin Vogel 22e917db52 wip: shared dense sqlite page-cache slab
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 16:38:42 +02:00
Martin Vogel 4004a25a80 wip: fail-loud allocator setup verification
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 16:04:11 +02:00
Martin Vogel 383bc8e9a7 wip: probe and complete allocator preloading on windows
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 15:45:09 +02:00
Martin Vogel ec57dbb97f diag: capture the arena slice map with the allocator stats
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 15:30:52 +02:00
Martin Vogel 3a381545ec diag: capture allocator stats alongside the snapshot
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 15:20:17 +02:00
Martin Vogel 063d312698 wip: collect at store release now that pages are mimalloc's
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 15:07:00 +02:00
Martin Vogel 30327de46f wip: audit allocator ownership per size class
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 14:53:15 +02:00
Martin Vogel d7d3ae407e wip: scope wrap flags, fix msize recursion
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 14:29:15 +02:00
Martin Vogel 1c71b99774 wip: route windows allocations through mimalloc via --wrap
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 14:25:40 +02:00
Martin Vogel 6cefa40cd9 diag: report whether the allocator owns this build's allocations
Adds mem_malloc_owned to every diagnostics snapshot: a real malloc/free pair
probed with mi_is_in_heap_region rather than an inference from build flags,
because the Windows static-CRT override is defined at compile time yet can
fail to take effect at link time -- which is exactly what happens here.

This single bit distinguishes 'nothing is live' from 'the allocator does not
own this build', which are opposite conclusions drawn from the same zero
live_bytes. On Windows it reads false, which means every allocator tuning in
cbm_mem_init -- immediate purge, purge-decommits, page reclaim, arena policy
-- is inert for ordinary allocations there, while POSIX gets all of it. That
is the parity gap behind #581, and reading this bit first would have replaced
a long elimination hunt with one measurement.

Also reverts the interim _heapmin trim explored while localising #581. It cut
the ten-minute Windows soak from 1168 MB to 232 MB but never reached the
13-to-15 MB flatness POSIX achieves, and it cost roughly four times the query
latency because it walks the whole heap on the request path. Trimming a heap
the allocator should have owned treats the symptom; the override itself is
the fix.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 14:20:32 +02:00
Martin Vogel d8443bcc0d wip: report allocator ownership in the memory map
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 13:49:58 +02:00
Martin Vogel 512587f046 wip: periodic windows heap trim
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 12:53:07 +02:00
Martin Vogel f292cd2049 diag: attribute committed-memory growth to a code path, and name #581's
The allocator walk could say how much memory was live and in what size class,
but not who was holding it -- and when growth does not route through the
allocator at all, as on Windows, it could say nothing useful. Phase marks
close that gap using the one metric that does see such growth: OS committed
bytes. Each mark attributes the committed delta since the previous mark to the
previous label, so over many passes the label whose total climbs is where
memory stays.

The design is deliberately coarse and says so. Committed bytes are
process-wide, so a busy concurrent thread lands noise in whichever label is
open; signal comes from accumulation over many passes, never one delta. Marks
must bracket a path with no unlabelled gap, or growth hides in the gap -- so
the request path labels its tail 'idle' explicitly, which is also what makes
request-path retention distinguishable from background growth. Off unless
CBM_MEM_PHASES=1.

Pointed at #581 it localised the growth in two steps. At request granularity
dispatch_tool held +102 MB over 27 requests while scope begin/end and idle
were flat, which independently killed the theory that the growth was
time-based. Splitting the handler then put +88.4 MB of it in resolve_store
against +14.1 MB for the query body, with the matching release returning only
-35.9 MB. So each request resolves a store, commits ~3.4 MB, and gets ~1.4 MB
back: a ~2 MB net ratchet per request that never returns. That accounts for
the observed growth, for its scaling with corpus size, for why every tool
leaks identically, and for why only Windows shows it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 12:10:43 +02:00
Martin Vogel 73a15f93ac diag: add a memory map that reports what it cannot see
Growth investigations were guessing, because the diagnostics snapshot only
carried process totals: RSS and committed bytes say memory grew, never
where. The map adds allocator-live bytes and a size-class histogram to each
snapshot, so a distinctive class identifies the allocation without
per-callsite instrumentation.

The design constraint is honesty about coverage. Walking the allocator
reaches the aggregate heap plus this thread's heap and its abandoned pages,
which is not the whole process, and in a build where malloc does not route
through the allocator at all it reaches nothing. A per-subsystem tally
would report zeros there and read as a clean bill of health. So every
snapshot carries three independent totals and an explicit residual --
os_committed, allocator-live, residual = the difference -- and the triple
localises growth instead of implying it:

  live grows            leak on a walked heap; buckets say which size
  residual grows, flat  another thread, retention, or non-allocator memory
  both flat, RSS grows  mappings or stacks, not the heap

That guard paid for itself immediately. Pointed at the open Windows growth
in #581 it reported live=0 with zero blocks and the residual carrying all
of it -- not 'the heap is innocent' but 'this walk saw nothing', which is
the correct reading for a process demonstrably holding tens of megabytes.
Together with the growth surviving a build with the allocator override
compiled out, that says the Windows allocations under investigation never
reach the allocator, so an allocator-based map cannot localise them and the
next instrument has to sit lower down.

Covered by a test that allocates a known volume in a known size class and
requires the map to attribute it there, so the map can never regress into
silently reporting zeros.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 09:53:59 +02:00
Martin Vogel 0d46fffbaa fix(test-infra): bind the smoke fixture server without a reverse-DNS lookup
The macOS Intel leg failed the fixture contract on every run while every
other platform passed. Removing the readiness fsync did not change it; the
hardened diagnostics named the real cause on the first remote run:
waited=30.0s, exit_status=alive, port_file=absent, staged_temp_files=none,
empty startup log -- the process was healthy but had never reached
publish_port.

http.server.HTTPServer.server_bind() resolves socket.getfqdn(host). On a
host whose resolver does not answer for the bind address that call blocks
for the resolver timeout, so the constructor never returns and no port is
ever published. The fixture now binds through a subclass that keeps the
threading server but skips the FQDN resolution, which only feeds CGI-style
variables this fixture never serves.

Proven both directions locally by forcing socket.getfqdn to hang: the
subclass publishes its port immediately, the stock server never does. That
forcing hook is kept as a permanent contract guard, so the reverse-DNS
dependency cannot return without turning the gate red on every platform
rather than on one runner nobody can reproduce.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 01:01:03 +02:00
Martin Vogel 636dd3f92c fix(test-infra): repair the native Windows soak transport and release checks
The native Windows soak could never start its server, so that gate had
never actually measured anything. The root cause was the harness, not the
daemon: an MSYS filesystem FIFO feeding a native Windows process delivers
empty stdin (reproduced 5/5), while a Bash anonymous coprocess carries the
full JSON-RPC initialize exchange to the same protected payload, and the
failing daemon log showed an orderly client disconnect rather than a
daemon-side eviction. The soak now uses a named coprocess on native
Windows only, duplicating its endpoints to stable fd3/fd4 and closing the
originals so closing fd3 still delivers EOF; POSIX keeps the FIFO path.
The coproc syntax sits inside eval because macOS system Bash 3.2 must
still parse this file even though only MSYS2 Bash 5 executes that branch.

With transport repaired the next exact failure surfaced: native Windows
Python cannot open an MSYS /c/... diagnostics path. Both consumers now
pipe the file through stdin, the pattern already established elsewhere in
the repo, and the soak recovery contract forbids reintroducing a direct
native-Python open.

Also on the release path:

- The smoke fixture server no longer fsyncs before atomically publishing
  its port. This is ephemeral readiness signalling, not crash-durable
  state, and the macOS Intel runner failed inside that durability sync.
  When the contract does fail it now reports the observable state --
  waited, exit status, port file, staged temp files, interpreter, startup
  log -- because the previous verdict named nothing on the one runner we
  cannot reproduce locally.
- The POSIX publication test hook no longer compiles into Windows builds,
  fixing an exact -Werror unused-variable failure. The setter keeps a
  parameter-consuming Windows stub because it is public API.
- The Wine leg assembles the real release layout (payload plus canonical
  launcher) and version-checks both, running the launcher through cmd so
  it has a Windows-visible parent. The unsupported Wine soak is removed:
  Wine stays a fast compile/package/version check, and native Windows is
  authoritative for daemon, locking, ACL and process-lifetime semantics.
- run.sh soak-windows routes to the native Windows VM, which validates the
  payload, builds the protected per-user temp root, stamps ACLs, and
  refuses success without a completion summary.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-25 00:37:05 +02:00
Martin Vogel 62821375c4 fix: harden daemon release paths and verification
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-24 16:08:42 +02:00
Martin Vogel 97ce23f982 Merge pull request #1230 from DeusData/fix/dry-run-determinism
Deterministic QA gates, dry-run fixes, and graph-quality repairs
2026-07-23 20:06:31 +02:00
Martin Vogel 50d0cc8c7d test: free the harness-built return-type table in the parity runners
The parity harness drives the pipeline passes with a bare ctx and never
ran the production teardown, so the return-type table pass_calls builds
leaked — latent until the fixture gained typed Java methods, and visible
only under Linux LeakSanitizer. Both runners now free it exactly the way
pipeline.c does.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 18:58:53 +02:00
Martin Vogel 1f50c9d46c build(test-infra): pin container base images and package downloads by hash
Resolves the two open Pinned-Dependencies code-scanning alerts (and one
sibling the scanner had not flagged yet):

- ubuntu:noble in Dockerfile and Dockerfile.lint is pinned to its
  multi-arch manifest-list digest (verified against the registry and the
  scanner's remediation digest, which match).
- mstorsjo/llvm-mingw drops the floating :latest and pins the current
  digest the same way.
- The MSYS2 zlib sysroot package in Dockerfile.mingw is verified against
  a pinned sha256 before extraction instead of piping the download
  straight into tar.

Dockerfile.alpine was already pinned; both rebuilt images (test, lint)
verified building with the new digests.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 17:49:41 +02:00
Martin Vogel 4a3aac6478 fix(pipeline): one Interface edge split in both venues, explicit overrides
The parallel per-file resolve hardcoded INHERITS for every base-class
relation while the sequential semantic pass made the Interface split, so
any corpus large enough for the parallel path had its explicit implements
demoted: elasticsearch carried 11,179 INHERITS edges pointing at Interface
nodes and 3 IMPLEMENTS. Both venues now share one decision keyed off the
target node's label — the graph truth the edge attaches to.

OVERRIDE edges existed only for Go's implicit interface satisfaction. A
serial full-graph tail run by both pipelines now matches class methods
against explicit bases by name and emits Method-to-Method OVERRIDE edges
for the explicit languages, with Go excluded to avoid double cover.

The venue parity fixture gains a Java interface/implements/extends trio —
IMPLEMENTS parity used to compare 0 == 0 and guarded nothing — plus an
exact-count test so the parity can never go vacuous again. At scale:
elasticsearch IMPLEMENTS 3 to 11,182 and OVERRIDE 0 to 49,695, TypeScript
0 to 4,496 and 594, dotnet/runtime 0 to 12,953 and 45,667, with INHERITS
plus IMPLEMENTS sums conserved exactly.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 17:36:00 +02:00
Martin Vogel 03dc9c916a fix(discover): grow the walk stack instead of failing wide repositories
A directory with more immediate subdirectories than the fixed 512-frame
walk stack made the whole discovery walk hard-fail with files=0 and a
misleading 'check repo_path' hint — dotnet/runtime carries 855 sibling
dirs in one JIT regression folder and could not be indexed at all. The
stack now grows geometrically from the same initial capacity; the parent
frame is a popped copy, so growth cannot dangle it. Covered by a 600-dir
fanout reproduction.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 17:36:00 +02:00
Martin Vogel b98f6a99c4 test: sanitizer-keyed budgets and a causality-based httpd interruption proof
The build system now stamps CBM_SANITIZED_BUILD for any non-empty SANITIZE,
so timing budgets key off build-system truth instead of compiler-specific
__SANITIZE_ADDRESS__ probes that miss trap-UBSan and TSan; the two bench
suites use it.

The httpd interrupt test proved 'the interrupt worked' with a wall-clock
bound, which is a lottery under sanitizers. A send-deadline test hook pins
the deadline out of reach so the join itself is the causality proof, and
the stop watchdog becomes a true ten-second hang detector.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 17:36:00 +02:00
Martin Vogel f7a8e373f4 fix(qa): make crash and tolerance gates fail loudly
The fuzz harness swallowed the target's exit status through '|| true', so
a SIGSEGV counted as a pass; the status now propagates (a planted crash
yields 139) and payloads derive from a logged, replayable seed, with a
missing python3 failing the gate instead of generating zero mutations.

The Windows guard runner classified unknown exit codes as skips; the
contract is now explicit (0 green, 1 red, 2 precondition-skip, anything
else a failure) and an all-skip run fails as verifying nothing.

The smoke suite gains crash-class detection on the phase-9b tolerance
paths and phase 11 kill handling (rc >= 128 or a missing jsonrpc banner
fails), a free-port pick plus readiness poll for the UI phase, and a
phase-15b failure that actually exits nonzero.

The soak reader resynchronizes on late JSON-RPC responses by draining to
the matching id, and the analysis fails when fewer than 60 percent of
snapshot attempts produced rows — a vacuous analysis previously passed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 17:35:40 +02:00
Martin Vogel f7231aa2d9 test: harden the parallel runner's stamp and completion accounting
The Windows build-dir DACL stamp resets inherited ACEs before granting,
verifies itself out loud, and re-stamps at the serial-tail boundary — the
arm image ships explicit ACEs that survived a grant-only stamp and turned
the whole suite red. Stamp verification failures now fail the run instead
of warning. A suite that exits 0 without printing its summary line is
counted as a failure rather than silently passing.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 17:35:40 +02:00
Martin Vogel 87f7c0d784 daemon: deterministic job interleavings and queued explicit index admission
Two changes to the index coordinator, developed together and validated as
one state:

Determinism seams. A cancel racing a worker start was only observable on a
loaded runner; now the interleaving is forced by construction: a test hold
parks every job thread before its first pre-start cancel check, and a
background-initialize completion counter turns sleep-then-assert negatives
into positive-signal waits. The cancel-before-worker-start case is pinned
by a dedicated test; three auto-index admission tests now wait for the
admission tail instead of sleeping through it.

Queued admission. An explicit index_repository request that hits the
physical job limit used to surface a raw busy error. The request thread
already blocks for the whole index, so it now queues: a cancellable,
shutdown-aware wait-and-resubscribe loop replaces the error path, with a
wait counter as the parked-state observable. The programmatic coordinator
API keeps its non-blocking busy answer for callers with their own retry
policy (watcher, auto-index). The job-limit test asserts the new contract:
parked without a second worker start, admitted and completed once the
occupying job finishes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 17:35:23 +02:00
Martin Vogel 5e7d3eb77e Merge pull request #1224 from DeusData/fix/daemon-app-tsan-wait-budget
test: size the daemon-application wait budget for TSan's slowdown tail
2026-07-23 09:53:45 +02:00
Martin Vogel e06d1b520e fix(daemon,win): give conflict-log share collisions a contention budget
The private-file prepare treated slow CONTENTION like permanent
OBSTRUCTION: one 250 ms deadline for both. With many concurrent
appenders on a loaded, sanitized runner the last writer can
legitimately wait longer than that behind its peers' brief exclusive
windows, and the concurrent-append guard correctly failed a dropped
conflict event (dry run: one appender of the pack lost). Transient
share/lock violations now retry against a 2000 ms contention budget
while every other error class keeps the 250 ms fast-fail — a rejected
client is still waiting behind this on the hello path.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 09:02:17 +02:00
Martin Vogel dc5649d1e6 test: re-stamp the Windows build-dir DACL at the serial-tail boundary
On one dry run the two arm shards split: the shard whose serial tail
held the install-flow suites failed the source-directory policy minutes
after its pre-wave stamp, while its sibling — same runner image, same
stamp — passed with no install-flow suites to notice. Wave suites spawn
Cygwin-family tooling that can rewrite the build directory's DACL
behind the first stamp, so the stamp is now a function invoked both
pre-wave and at the tail boundary, where the deadline-sensitive suites
that depend on it actually run. Both invocations self-verify out loud.

Verified on the VM from a deliberately reset (inherited) build
directory through the real --par path: both stamps report clean, mini
shard green.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 08:54:56 +02:00
Martin Vogel acce9a4582 test: make the Windows build-dir DACL stamp verify itself out loud
The dry run's windows-11-arm shard failed its install-flow tests with
the source-directory refusal the stamp exists to prevent — while the
identical commands, run from the identically dirty state on the ARM64
VM, stamp clean and pass. The difference is invisible because the stamp
was fully silenced; a silent load-bearing step cost a full CI round to
even learn whether it had run.

The stamp now reports each icacls failure with the user and directory,
re-inspects the DACL afterwards, and prints one line in either
direction: stamped clean, or the surviving cross-account grants.

Verified on the VM from a deliberately reset (inherited, Authenticated-
Users-writable) build directory through the real --par path: stamp
reports clean, suites green.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 08:49:35 +02:00
Martin Vogel 2af28c9d48 test: size the daemon-application wait budget for TSan's slowdown tail
The suite's observation waits shared a 2000 ms budget that is a hang
detector, not a latency assertion — every waiter returns the moment its
condition holds, so green runs never pay it. Under ThreadSanitizer's
5-20x slowdown on a loaded 3-core macOS CI runner the cancel-delivery
wait lost the tail of that distribution once in seven otherwise-green
TSan rounds (the cancel itself had succeeded; only the fake worker's
counter observation timed out). The budget is now 10000 ms.

Verified: nine consecutive daemon_application runs under native macOS
TSan with the widened budget, all green, zero ThreadSanitizer
warnings.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 08:42:30 +02:00
Martin Vogel 5a5ed51f32 Merge pull request #1223 from DeusData/fix/win-arm-sanitize-arg-quoting
ci,test: keep a spaced SANITIZE value one make argument end to end
2026-07-23 08:18:55 +02:00
Martin Vogel 9fb3bdaff1 ci,test: keep a spaced SANITIZE value one make argument end to end
The windows-11-arm leg's first execution (broad matrix, release dry
run) died before any test ran: the trap-UBSan SANITIZE value passed
unquoted through the workflow ternary, so -fsanitize-trap=undefined
arrived as its own word and make consumed the leading -f as its
makefile flag ('No rule to make target sanitize-trap=undefined').

- The workflow quotes the ternary value so test.sh receives one
  argument.
- test.sh forwards make arguments through an ARRAY instead of a
  re-split string, so a VAR=VAL whose value contains spaces survives
  every boundary (empty-array expansion kept bash-3.2-safe for the
  macOS runners under set -u).

Verified: argument-parse proof (argc=1 with the four-flag value) and a
full macOS suite run through the changed plumbing, 6776 passed,
0 failed — identical totals.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 07:11:38 +02:00
Martin Vogel dc7178c8dc Merge pull request #1139 from DeusData/feat/shared-coordination-daemon
feat: coordinate concurrent CBM sessions
2026-07-23 03:52:52 +02:00
Martin Vogel e4400b6f49 fix(smoke): retire fixture daemons before every install-fixture cleanup
Phase 9b's fixture installs spawn ephemeral daemons whose mapped
generation backings pin files in the fixture HOME; the cleanup rm then
races the daemon's asynchronous drain and loses nondeterministically on
Windows (Device or resource busy on a generations backing — the same
class the final-phase daemon retirement already fixed for the smoke
cache, two rounds green then one red on identical code). The retirement
is now a shared helper with a cheap not-running probe, called before
every fixture cleanup that follows an install and by the final phase; a
daemon that will not retire stays a hard failure.

The 9b fixtures also route LOCALAPPDATA under the fixture HOME: their
installs staged launcher pairs into the real runner profile, outside
the fixture's isolation and the harness's cleanup reach.

Verified: full VM smoke green end to end (all 9b subphases, phase 17
through the shared helper).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 02:33:38 +02:00
Martin Vogel 04141465ee test: stamp the Windows build-directory DACL inside the parallel runner
The workflow pre-created build\c with a protected DACL so build
products would inherit it, but the diagnostic note from the failing CI
shard named the real actor: MSYS2's Cygwin layer writes POSIX-emulating
DACLs — including a CREATOR OWNER (S-1-3-0) mutation grant — onto
directories its tools touch, overwriting the pre-created shape during
the build. The activation transaction's source-directory policy
correctly refuses that grant, so the install-flow tests failed on CI
while the VM (whose harness stamps AFTER building) stayed green.

The stamp now runs inside run-tests-parallel.sh on Windows, after any
builder has had its say and immediately before the suites — one code
path for CI and the VM. Two idempotent steps, as before: protect the
directory, then /reset the children onto the clean inherited set.

Verified: full VM parallel run green (6669 passed, 0 failed) through
the new path.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 02:12:17 +02:00
Martin Vogel 568e613141 test-infra: overlap the local ladder, open the serial tail, keep container I/O off virtiofs
Local runs left most of the machine idle: the serial tail ran sixteen
suites one at a time on otherwise-idle cores, the three platform legs
were launched by hand (usually sequentially), and every container-leg
object write crossed the virtiofs bind mount.

- run-tests-parallel.sh tail scheduling in two phases: the FLEX suites
  (timing-shaped but free of the shared per-account daemon runtime
  namespace) run CBM_TAIL_JOBS-wide (default 2), then the EXCL group —
  daemon-family plus the suites that drive daemon one-shots or
  supervisor rendezvous — runs strictly sequentially on a machine
  exactly as quiet as the old fully-serial tail gave it. The wave was
  already fed longest-first by the shard dealing order, so the drain-out
  no longer ends on a heavy straggler.
- ladder.sh: one maintained entry point for the full local push gate
  with the legs overlapped — lint, the Linux container suite, and the
  Windows VM suite in the background, the macOS suite in the
  foreground, one verdict per leg, logs kept per leg. A missing
  prerequisite fails its leg loudly instead of silently skipping.
- win.sh test-par now runs through vm-run-tests.sh (--par mode): the
  full parallel harness under the CI-shaped protected temp root with
  complete output. It previously ran under the MSYS-shared /tmp and
  piped through `tail -25` — the same truncated-blindness class that
  hid 40 Windows failures from the `test` command.
- docker-compose: build artifacts and the incremental fixture cache
  move to named volumes on the container VM's native filesystem. Object
  writes over the virtiofs bind mount are the container legs' largest
  avoidable I/O cost, and the fixture cache now survives across
  container runs.
- test_mem(win): the first valid full-parallel VM run proved working-set
  trimming beats the re-touch mitigation (19 MB resident of a 256 MB
  double-touch at 18 parallel suites). The RSS probe now VirtualLocks a
  64 MB span — locked pages are exempt from trimming, making the
  measurement pressure-immune — with bounded touch-and-sample retries
  when the lock is unavailable. Red-to-green under the same 18-job load.
- cli: the portable install's staging error now appends the activation
  refusal note (predicate, SID, object) like the managed path already
  does — a bare "activation transaction I/O failed" on a CI-only
  failure is undiagnosable without it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 01:33:58 +02:00
Martin Vogel d7096e7938 ci: switch PR, dry-run, and release validation to the sharded topology
Flip shard_suites on for all three callers of the reusable test
workflow. This commit is deliberately the only topology change:
reverting it alone restores the previous single-job-per-leg CI shape
while keeping the (inert without it) sharding machinery and the
cache-warm pipeline.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 00:53:52 +02:00
Martin Vogel d7ce789e8f ci: shard the C-suite legs, prove cross-shard coverage, warm compiler caches
PR iteration was bounded by single-job suite runs (~40-50 min critical
path) and by every first-of-ref build starting cold. Same suites, same
sanitizers, same gates — the schedule, the cache reuse, and a new
runtime coverage proof change.

- run-tests-parallel.sh accepts CBM_TEST_SHARD="i/N" and runs a
  deterministic slice of --list-suites; unset selects everything, so the
  path is inert outside CI. Known-heavy suites are dealt to shards in
  weight order first (naive modulo stacked the two slowest, store_arch
  and daemon_runtime, onto one job), the parallel wave and the serial
  tail are sliced separately so every shard keeps its own quiet tail,
  and the per-shard union guard proves each job ran exactly its slice.
- shard-completeness job: per-shard guards cannot see a mis-plumbed
  CBM_TEST_SHARD (two jobs running the same slice passes every local
  check while a slice runs nowhere). Every leg now uploads a manifest
  (leg, i/N, sha of the full suite list, its slice) and one aggregation
  job re-proves per leg that the shards agreed on the list, indices are
  exactly 1..N, and the union of slices IS the list. Runs on unsharded
  topologies too, where each leg's single manifest must cover the list.
- workflow topology (shard_suites input, off by default = byte-identical
  to the pre-shard matrix): ubuntu legs run 3 shards, Windows 2 (every
  extra Windows shard re-pays ~5 min of MSYS2 setup), macOS stays at 1
  (not the critical path; mac runner concurrency ceilings are the
  tightest). The Windows test job also pre-creates build\c with a
  protected DACL so build products inherit it — workspace drive roots
  grant Authenticated Users Modify by inheritance, which the activation
  transaction's source-directory policy correctly refuses.
- ccache: a final ref-less restore key lets a fresh PR start from the
  newest cache GitHub's scoping permits, and a nightly build-only
  cache-warm workflow keeps main-scoped caches at most a day stale —
  without it the fallback had no warm source, since nothing built in
  main's scope. The strictly-per-ref policy this replaces cost ~8-12
  minutes on every first-of-ref build for no safety gain:
  CCACHE_COMPILERCHECK=content makes a stale or foreign cache able to
  miss but never to return wrong output. Sharded legs restore-all/
  save-one (every shard builds the identical objects, so shard 1's
  cache carries the full set) — save volume stays flat and warm caches
  stop being evicted by per-shard duplicates.

- suite timeouts: daemon_runtime joins the slow tier. It measures ~610s
  solo on arm64 under ASan (10-round loop, no hang), so the 900s default
  under a loaded 4-job CI runner was a slowness kill masquerading as a
  hang detector.

Validated locally: 3-shard union over the real suite list is complete
with no duplicates, one top-heavy suite per shard, and the summed
3-shard totals reproduce the unsharded run exactly (6776 passed,
0 failed, 4 skipped).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 00:53:52 +02:00
Martin Vogel f1beaec4d6 test: cache the incremental fixture and index the CI-shaped corpus everywhere
The incremental suite cloned fastapi from the network on every run and
indexed a DIFFERENT corpus per environment: CI used a sparse checkout
without docs/ and tests/ (the assertion thresholds are sized for it)
while local runs indexed the full tree — twice the files for the same
assertions, a per-run network dependency, and the suite's dominant cost
on the sanitized container leg.

The fixture is now cloned from the network at most once per machine
into a persistent cache (committed with an atomic rename so a torn
download can never pose as a valid cache; CBM_TEST_FIXTURE_CACHE
overrides the location), every run local-clones from it offline, and
docs/ plus tests/ are trimmed everywhere — the portable equivalent of
CI's historical sparse profile, so all environments now index the same
corpus the assertions were written against.

Same 163 tests, same assertions: 74s -> 31s native, with the container
leg's cut scaling with the halved indexing volume, and the "network
offline" flake source reduced to first-run-per-machine.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 00:53:52 +02:00
Martin Vogel 664058e86b test-infra(win): CI-shaped VM harness, managed-install smoke gate, calibrated suite tier
The 40 hidden Windows failures fixed in the previous commit reached CI
unseen because the local Windows leg never validly ran the C suite:
MSYS2's shared /tmp fails the secure-ancestry validation (refusals, not
signal) and the ssh driver piped everything through `tail -40`. This
closes those holes and hardens the gates that let an install failure
scroll past.

- vm-run-tests.sh (new, used by win.sh test / ubsan-test /
  trap-ubsan-test): gives the suites CI's exact protected per-user temp
  root (owner-stamped, protected current-SID DACL, mirroring the
  workflow's pwsh step), shapes the runner's build directory like a real
  user checkout (drive-root trees inherit an Authenticated-Users Modify
  ACE that profile-rooted checkouts do not have, which the activation
  source-directory policy correctly refuses; DACL re-rooted two-step —
  directory protect then child /reset, since inheritance flags are
  directory-only and a /T re-root leaves files with empty deny-all
  DACLs), streams the FULL output, and refuses to report success unless
  the runner printed its completion summary.
- smoke: Phase 8 no longer swallows the install exit code. On Windows it
  now requires the managed install to SUCCEED and to publish an exact
  two-link canonical launcher — a staging refusal used to scroll past as
  tolerated noise while the downstream config assertions kept passing.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 00:53:51 +02:00
Martin Vogel f51745533c fix(win): reconcile the C suite with the managed-launcher contract
The first full Windows x64 CI run of this branch (earlier contract-step
walls had always aborted the leg) surfaced 40 failures: the managed
launcher's fail-closed install/update/uninstall gates and the suite's
POSIX-modeled activation tests were both new in this branch and had
never been reconciled, and they hid three real production bugs.

Production fixes:

- ui: config publishes with POSIX rename semantics. MoveFileExW's legacy
  replace cannot supersede a destination a reader holds open (the name
  lingers until the last handle closes, even with FILE_SHARE_DELETE), so
  a config save racing an open reader failed. The temp file is opened
  with DELETE access and renamed in-hand (bare-path target, NUL-padded
  rename record) before close, falling back to MoveFileExW on any
  rename-by-handle failure so non-NTFS config homes keep the previous
  behavior.
- subprocess(win): the child's redirected log handle now shares write
  access. POSIX places no lock on the redirected log, so a second
  producer may append while the child runs; the missing FILE_SHARE_WRITE
  mandatory-locked every other writer out, which the index-supervisor
  callback-isolation test legitimately exercises.
- cli(win): the portable uninstall path composes the managed binary name
  with its .exe suffix; it could never find (or remove) the installed
  binary before.
- activation: security-predicate refusals are now diagnosable. The
  transaction's predicates refuse without a usable OS last-error, which
  collapsed five distinct refusal classes into one blind "status -3,
  os 0" at every caller. Each refusing predicate records what refused —
  predicate, untrusted SID, and object path — first-wins so the deepest
  cause survives, and the staging error line appends it. This is what
  identified every Windows refusal in this batch, including the real
  install-failure class on drive-root-rooted payload directories
  (Authenticated Users holds inherited Modify there; profile-rooted
  directories do not).

Windows test dispatch (release binaries unchanged):

- Test builds (CBM_CLI_ENABLE_TEST_API, the existing test-object macro)
  compile the portable activation flow on Windows, reachable solely
  through the installed activation test seam and announced loudly on
  stderr; release binaries compile exclusively the managed transaction,
  so the dispatch polarity fails closed. The suite's install/update/
  uninstall calls route through a seam-if-absent wrapper, keeping the
  shared activation and agent-config semantics unit-covered on Windows;
  POSIX arms keep exercising the real activation machinery.
- Two new release-gate tests assert the fail-closed contract with the
  seam OFF: a portable payload may not self-update/self-uninstall, and
  install refuses to adopt or replace a foreign file at the canonical
  launcher path even under --force.
- Test-bug fixes the first valid Windows runs exposed: the version-
  cohort crash test reused one conflict record across the probe and the
  turnover acquire (whose entry memset erased the probe's detail before
  the assertions read it); the activation staged-path assertion now
  checks containment rather than a separator (the \\?\ namespace
  performs no forward-slash translation); the ui atomic test captures,
  cleans up, then asserts so a failure cannot poison later tests; the
  PATH-visibility guard is POSIX-only (Windows configures PATH through
  the user registry, not a shell rc).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-23 00:53:41 +02:00
Martin Vogel 6ab5e3fa79 fix(smoke): retire the account daemon before the harness ends
The account daemon legitimately outlives its last client for a moment
while it drains, holding its log and config DB open. POSIX rm does not
care, but on Windows an open file blocks deletion, so the caller's
cleanup of the smoke cache raced the daemon's asynchronous shutdown and
failed with "Device or resource busy" after every phase had passed —
first visible on windows-latest once the agent-config phases stopped
failing earlier, and reproduced identically on the local Windows VM.

The smoke now ends with an explicit phase: stop the daemon through its
own lifecycle command, wait (bounded) until status reports not-running,
and give Windows one beat for the final handle close. A daemon that
will not retire is now a smoke FAILURE in its own right instead of a
cleanup accident. Verified red-to-green on the VM and green on macOS.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-22 18:27:08 +02:00
Martin Vogel ece838c525 test: widen ThreadSanitizer coverage and add native ARM64 Windows UBSan
Close the cross-platform sanitizer gaps that were leaving real
concurrency and undefined-behavior bugs uncaught, and fix a data race
the first widened run surfaced.

- tsan: the data-race gate ran three suites (mem, slab_alloc, parallel)
  over no real threaded production code. It now covers every threaded
  surface that runs clean and stable under TSan: the parallel-extraction
  worker pool (parallel, worker_pool, pipeline), the filesystem watcher,
  the embedded HTTP server (httpd), diagnostics sampling, the MCP server
  and mutation guard, subprocess supervision, and the runnable
  daemon-coordination paths (daemon, daemon_application). daemon_runtime
  (deadlocks under TSan+fork), daemon_ipc and daemon_frontend
  (test-harness synchronization, not production) are excluded with the
  reasons recorded in the Makefile.
- tsan: fixed a genuine data race the widened gate immediately found —
  cbm_lsp_max_walk_depth's lazy cache was read and written by parallel
  LSP-extraction workers without synchronization. A data race is
  undefined behavior even when every worker computes the same value, so
  the cache slot is now a relaxed atomic: a plain load on the hot path,
  and a first-touch double-compute simply stores the same value.
- tsan(ci + local): the test-tsan job now runs on Linux amd64, Linux
  arm64, AND native ARM64 macOS (the threading code is shared, so a race
  is usually caught on all three, but scheduler differences let each
  surface one the others miss). The local ladder gained `run.sh tsan`
  and `tsan-amd64` plus the matching compose services. TSan's shadow
  memory aborts under modern high-entropy ASLR, so the containers run
  under `setarch -R` with an unconfined seccomp profile (the personality
  syscall is otherwise blocked) and the CI Linux legs lower
  vm.mmap_rnd_bits first; amd64 TSan cannot run under x86_64-on-ARM
  translation and is a real-hardware/CI gate only (documented in
  run.sh).
- ubsan(win/arm64): native ARM64 Windows had no sanitizer at all —
  AddressSanitizer ships no aarch64-w64-windows-gnu runtime. UBSan in
  trap mode (-fsanitize-trap=undefined) needs no runtime library, so it
  instruments natively and turns undefined behavior into an
  illegal-instruction trap; -fstack-protector-strong adds stack-smash
  coverage the heap tools miss. The GitHub windows-11-arm leg switches
  from unsanitized to this, and vm/win.sh gains trap-ubsan-build /
  trap-ubsan-test for local iteration (reproduce under the emulated
  x86_64 UBSan to see which check fired). The whole codebase builds and
  runs clean under it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-22 17:48:54 +02:00
Martin Vogel 34f9ad1b3d fix(daemon,win): CI-green fixes and a daemon-stability guard
Fixes surfaced while bringing the shared-coordination-daemon branch
green across macOS, the Linux containers, and the real Windows-ARM64 VM,
plus a new stability guard that caught one of them.

- daemon(win): the CLI teardown transition latched a failed release as
  permanent, but a Windows participant-state release must briefly
  try-hold the shared startup/legacy gates and legitimately collides
  with a concurrent one-shot's teardown; the release is retriable by
  contract (it always retains the transition), so parallel one-shot
  commands no longer report "CLI coordination cleanup failed" despite
  succeeding. Found by the new stability guard's churn section.
- cli/main(win): install/update/uninstall and the Augment hook-script
  removal derive the managed launcher's identity from its plain
  drive-form path (cli_windows_plain_utf8), not the \\?\ extended-length
  form, so agent-config ownership matches on Windows and uninstall no
  longer leaves the MCP entry or owned hook scripts behind.
- security: the `daemon start --open` browser launch is now shell-free
  (ShellExecuteW on Windows, cbm_exec_no_shell elsewhere) instead of
  system(); the CORS origin check spells out its two literal loopback
  URLs so the static URL audit sees a complete value. Both clear the
  Layer-1 allow-list audit.
- coordination: version-cohort lock retries now sleep a per-process
  jittered interval — fixed-period retries can phase-lock two
  participants so one starves. The activation-quiesce test's
  observation window now covers worst-case candidate staging (three
  tamper-defense hashes over a ~1 GB sanitizer binary exceed the old
  30 s budget on container I/O).
- smoke/soak: the agent-config smoke matches Windows config paths
  through their escaped-backslash quoted form; the soak parser skips the
  update-available banner before the JSON summary and gained a one-shot
  CLI admission-churn phase inside the RSS/FD leak window. The build-dir
  safety fixture creates a real symlink/junction (MSYS2 ln -s otherwise
  deep-copies) so its traversal-refusal contract is genuinely exercised,
  and the symlinked-agent-roots test demotes its planted links to an
  unprivileged owner when run as root so the refusal is really tested.
- tests(win): a new daemon-stability guard exercises the parameter
  surface, hook fail-open with its rate-limited notice, start-twice and
  occupied-port handling, busy-stop refusal against a live MCP session,
  kill -9 crash recovery, and sequential/parallel one-shot churn.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-22 17:48:20 +02:00
Martin Vogel 30550fbf75 test-infra(vm): restore the executable bit on win.sh lost in a merge resolution
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-21 15:32:42 +02:00