adacovex 1.28.0¶
Date: 2026-08-25
Version bumped 1.27.0 -> 1.28.0.
Changes¶
C1: perf.md updated with measured numbers and optimisation history¶
docs/perf.md now records the 1.28.0 benchmark figures (cold ~0.6 s, warm
~40 ms on the self-assessment, 9.0 MiB binary / 3.6 MiB stripped), the new
“when the numbers regress” workflow, and the full optimisation history in
reverse-chronological order so each future round can see which assumption
drove the previous change.
C2: Stamp fast-path hashing for the scan cache¶
The warm-path CPU profile showed GNAT. SHA256 at ~48%: every .ads file was fully re-read and re-hashed on every run just to compute its cache key, even when the content was unchanged. Adacovex. Cache. Hash_File now keeps an in-memory path -> (size, digest) stamp map and serves the recorded digest without opening the file when the size still matches.
The fast path is per-process (the map is not persisted), so an edited file always re-hashes: its size differs or the map is empty. The same map means the SBOM tree-walk hashing reuses the digests the source scan computed in the same run, collapsing two hash passes into one read plus one size check. Warm user time dropped by roughly a third on the self-target.
C3: SBOM system-tool referenced-set cache¶
Discover_System_Dev_Deps walked the whole project tree and word-scanned every dev-facing file on every run to collect the system-tool dev dependencies for the SBOM. A profile showed that word scan at ~62% of warm CPU. The referenced-tool set is now cached on disk under a key that covers every input the scan reads: the content hash of exactly the files the scan reads (same Should_Scan predicate and directory exclusions as the main walk), the curated tool-table names (in case the table changes within a release), and a namespace salt. An unchanged project serves the set from one small cache blob and skips the tree walk and every file read; any edit to a scanned file changes the key and forces a re-scan.
Cold runs pay one extra tree walk (about 8% of cold); warm runs skip the entire SBOM scan, taking the warm self-assessment from ~58 ms to ~39 ms. A unit test round-trips the cache: a second discovery on the same fixture and cache dir agrees exactly with the first, including tools that are absent from PATH.
C4: Formal-containers experiment confirms the two SPARK_Mode Off exceptions¶
The Adacovex.Complexity package was probed as a candidate for moving to
SPARK-approved (formal) containers so its SPARK_Mode (Off) could be
removed. Scratch units under gnatprove 16.1.0 show this is not viable:
The GNAT 16.2.1 runtime formal containers (
Ada. Containers. Formal_Vectorsand friends) arepragma Compile_Time_Errorstubs; the implementations moved to the SPARK library shipped inside the gnatprove toolchain (`SPARK. Containers.
Formal.*`).
Adopting them would make the crate depend at build time on the SPARK library, breaking the zero-dependency contract: gnatprove is resolved at run time by the
provesubcommand, but the containers would be a compile-time dependency.The
Complexityaggregates embed vector components; the formal containers’ limited privateVectorcannot be a record component, so a cursor-iteration data-flow rewrite would be required on top of the library change.
Verdict recorded in docs/proof/16.1.0-ledger.md: Adacovex. Complexity and Adacovex. Types. Implementation remain the only two SPARK_Mode (Off) packages, both for the same reason (non-formal `Ada.
Containersinstantiations), andspark-off-check` allows exactly these two.
C5: Makefile parsing moved to pure-Python tools¶
Several make targets formatted or parsed text with shell one-liners that
broke under some shells (awk’s “backslash not last character on line”
inside the bench size report) and used GNU-only filters (grep -P,
sed -i, sort -V) that do not exist on BSD/macOS. The parsing and
formatting moved to pure-stdlib Python scripts so the targets behave the
same on every platform:
tools/bench-size.py–make bench’s binary-size report (stats the files itself; no morestat -c+ awk quoting).tools/ascii-check.py– the ASCII gate (nogrep -P; tab and LF are allowed, CR still fails, so CRLF line endings are caught).tools/spark-off-check.py– theSPARK_Mode (Off)gate.tools/versions.py– version read /set-version/ version-aware filter (replacessed/sort -Vinrelease,test-publish,coverage-gate).tools/bump-version.py– the wholemake bump-versionrecipe (manifest rewrites, release-file/index scaffolding, changelog scaffolding, description sync).tools/filter-sframe.py– the build-log SFrame-notice filter (make build).tools/rst2md.py --prune-test-pages– drops test-page links from the API-docs index (replaces twosed -icalls inmake doc).
The recipes that remain in the Makefile are plain POSIX pipelines
(head, tail, grep -E, cmp, ls); there is no GNU-only filter or
inline awk left.
C6: system-tool probes moved to a stable machine-level store¶
The cold-path profile showed the SBOM system-tool version probes (each a subprocess; node/hg/mandb boot an interpreter) dominating cold runs. They were cached under the result cache, so wiping the cache or pointing --cache-dir elsewhere re-probed every tool. They now live in ~/.adacovex/probes/ (7-day TTL) outside the result cache, and the referenced-tools cache blob stores each tool’s probe result so a cache hit rebuilds the SBOM tool edges without PATH lookups, probe reads, or spawns. Cold on a warm-probe machine dropped from ~658 ms to ~86 ms (7.7x); warm from ~39.8 ms to ~34.6 ms.
The blob layout is names-|-version-pairs and Cache_Schema was bumped to s6 so names-only blobs from the earlier layout are never served as complete.
C7: Dashboard dependency graph: versions, direct links, real packages¶
The dashboard dependency graph (and the SBOM that shares it) no longer lists things that are not packages, and every listed package carries a version and a direct registry link:
The four vendored dashboard libraries (FlexSearch, nomnoml, graphre, Charts.css) are now versioned (
0.7.31/1.7.0/0.1.3/1.2.0) withpkg:npmPURLs. adacovex’s own dashboard sources (resources/dashboard.*) are not a package and never become components.node_modulesscanning resolves scoped packages (node_modules/@scope/pkgbecomes@scope/pkg@<version>from itspackage.json, so the e2e fixture yields@playwright/test@1.62.1) and skips pnpm’s virtual store and shim dirs (.pnpm,.bin) entirely..pnpmis not a dependency; thepnpmbinary is, and stays the dev tool. The SBOM and the dashboard now render the same list.The dep-details Link row builds direct GitHub / GitLab / Bitbucket / npm / crates.io / PyPI / pkg.go.dev / Alire links from the PURL (scoped npm names included) and no longer falls back to a GitHub search URL for unknown ecosystems.
C8: Proof categories match gnatprove’s summary table¶
gnatprove 16 reports flow analysis as “Data Dependencies” (checked) plus
“Flow Dependencies” (proved implicitly), and the dashboard’s Flow row
read only the Flow Dependencies row – showing 0/0 on the self-target
while gnatprove showed 56 checked data-dependency VCs. The parser now
sums both rows into the Flow category and computes every category’s
proved count as Total - Justified - Unproved (Termination previously
showed 73/94 instead of 94/94). The Proof tab table and the Charts
“Proof Check Types” column gained a Termination row, so the per-category
numbers sum exactly to gnatprove’s Total (56 + 5 + 407 + 107 + 55 +
94 = 724). Cache_Schema bumped s6 -> s7 (parser semantics).
C10: Makefile orchestration moved to dedicated Python tools¶
Beyond the pure parsing ports in C5, every multi-step make recipe now
delegates to a dedicated stdlib-Python script, so the Makefile is
declarative and each flow is individually testable:
tools/build.py– the build flow (gen-version+gen-dashboard+alr buildwith the SFrame log filter + thecovexsymlink).tools/dev-cmd.py– the alire-dev.toml swap behindmake doc/make fmt, restored unconditionally (including on Ctrl-C / SIGTERM). The swap uses a fresh-mtime copy:alr execre-synchronises the workspace only whenalire.tomlis strictly newer thanalire/alire.lock, so a metadata-preserving copy silently skipped the sync and gnatdoc / gnatformat never reached the exec PATH.tools/bench.py– the cold/warm hyperfine (or measured) benchmark plus the stripped-binary-size report.tools/coverage-gate.py– the release-tag docstring delta in a temporary worktree, removed even when the assessment fails.tools/run.py– the single owner of theprove/run-self/sbom/run-ada-crdtinvocation shape (SOURCE_DATE_EPOCH + acceptance gates);run.py assess-argsfeeds the release flow, so the gate flags live in exactly one place.tools/release.py– the whole release flow (prove, build, validate, coverage gate, changelogs, bundling, attestation, manifest bumps, tag/push), withmake release DRY_RUN=1running everything except the irreversible commit/tag/push.tools/tests.py– a stdlib-unittestsuite over the tools’ pure logic (26 tests), wired intomake checkas thetools-checkgate.
The ASCII gate now skips the gitignored generated Playwright output
(tests/e2e/playwright-report/, tests/e2e/test-results/), which
previously tripped the gate after a local make e2e run.
C11: gnatprove run is warning-free¶
The six [assumed-global-null] warnings from
CPUs.Get_Temp_Directory’s Ada.Environment_Variables reads are
silenced in the source: the function scopes a
pragma Warnings (Off, "no Global contract available") around itself
(re-enabled immediately after), which gnatprove honours by message
prefix. The proof surface is unchanged – still 724/724 VCs, 0
unproved, 0 justified.
C9: Dashboard chart, filter, diagram and credits fixes¶
Pie/donut data numbers are upright: Charts.css rotates each slice’s value by its midpoint angle, so a full proved ring (0..1 turn) rendered the “724 VCs” number rotated 180 degrees. Overridden to
transform: none.The test-category bar chart is sized to its category count (
--rows), so a 14-category suite no longer clips its last rows; column charts with more than eight categories rotate their labels vertically instead of overlapping. The Overview tests donut no longer overflows its card.The dep name filter and scope checkboxes actually work: the filter used
Mapbracket access (info[n]), which threw on every keystroke and aborted the run, so typing a name or unchecking a scope changed nothing. Fixed toinfo.get(n).The nomnoml diagram now derives every colour (fill, background, stroke, line, font, note) from the active theme’s CSS custom properties and re-renders when the theme changes, so light and dark no longer produce identical (default-yellow) diagrams.
The Credits tab fills the Playwright version from the resolved graph (
@playwright/test@1.62.1) instead of showing a static “dev”.
Fixes¶
H1: Cold-cache runs no longer re-probe the whole toolchain¶
Before 1.28.0-era cache wipes, a wiped --cache-dir re-probed every
referenced tool. After these changes a fresh result cache on a machine
with warm probes skips every spawn. First-run-on-machine cost unchanged.
H2: make e2e runs the dashboard suite again¶
The Playwright web-server wiring referenced a script that does not
exist (tools/start-dashboard-server.py), and the launcher computed the
repository root one level too deep – both fixed. The suite now runs
serially (a single-process dashboard server cannot serve a fully
parallel browser swarm), and the stale layout expectations were updated
for the current dashboard (card counts, scope badges, chart count,
credits links), plus new coverage for the dep name filter and the scope
checkboxes fixed in C9. 15 e2e tests pass.
Test Suite¶
973 tests passing across 14 categories (5 new: the tools-set cache
round-trip in the SBOM suite; the probe-store change keeps the same
count). The GNATprove parser tests cover both summary layouts (legacy
3-column and modern 6-column rows). 15 Playwright dashboard layout
tests pass via make e2e.
Proof Results¶
Platinum, 724/724 VCs proved under gnatprove 16.1.0. 0 unproved, 0
justified. CPUs.Get_Temp_Directory scopes a
pragma Warnings (Off, "no Global contract available") around its
Ada.Environment_Variables reads (the GNAT runtime carries no Global
contracts for them), so the run is warning-free.
Traceability¶
No new HLRs. Coverage:
HLR-CACHE– C2 stamp fast-path hashing, C3 tools-set cache, C6 probe-store change, C8 Cache_Schema s7 bump.HLR-SBOM– C3 tools-set cache keyed on the scanned file set storing probe results (C6), C7 dependency-graph package fixes.HLR-ARCH– C1 perf.md documentation, C4 formal-containers verdict, C5 Python-tooling port.RENDER-HTML– C8 proof-category consistency, C9 dashboard chart/filter/diagram/credits fixes, H2 e2e suite.
See docs/perf.md, docs/proof/16.1.0-ledger.md.