Skip to content

integration/intervals takes develop: the slim Windows toolchain, the MPFR thread-exit fix, Windows ccache, the random state and solution metadata JSON - #531

Merged
ofloveandhate merged 30 commits into
bertiniteam:integration/intervalsfrom
ofloveandhate:intervals/takes-develop-windows-toolchain-and-records
Nov 8, 2026
Merged

ofloveandhate merged 30 commits into
bertiniteam:integration/intervalsfrom
ofloveandhate:intervals/takes-develop-windows-toolchain-and-records

Conversation

@ofloveandhate

Copy link
Copy Markdown
Contributor

Brings develop into integration/intervals, so the interval work builds on what has landed there since the branch was cut:

It is a merge, not a rebase, as an integration branch takes its trunk. Two files conflicted, both lists where each side added entries: the 4.1.0 Added block of the changelog (FLINT here; the random state and solution metadata on develop) and the ADR index (0075 here; 0076 to 0079 on develop). Both sides are kept, in order. The workflow, core/CMakeLists.txt and environment-win.yml merged without conflict, and libflint stays in the slim Windows environment.

One commit beyond the merge: the Windows jobs print FLINT's, GMP's, MPFR's and MPC's versions with the other main conda packages.

What this run shows for the first time: FLINT linked by link.exe on Windows (#526 linked it with lld-link), and test_flint under clang 22.

🤖 Generated with Claude Code

ofloveandhate and others added 30 commits November 3, 2026 21:36
…r, and print why any call is uncacheable

Both Windows jobs run clang-cl through ccache, and on 2026-11-01 the wheel job reported 0 of 107 calls cacheable, every one uncacheable, so the saved cache was 12 KB and every run compiled cold (32 minutes for the C++ tests). The non-verbose stats do not say why. Two known refusals are addressed: ccache declines a compile that uses a precompiled header (the CMake cmake_pch) unless its sloppiness allows pch_defines and time_macros, and it must classify the compiler as clang-cl. The stats are now printed verbose, so if any call is still refused the next run names the reason.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… it refuses

With CCACHE_SLOPPINESS and CCACHE_COMPILERTYPE set, the C++ job still reported Unsupported compiler option for 149 of 149 calls, and the statistics do not name the option. The ccache log does, so both jobs now write it and print its first unsupported-option lines and the ccache version after the build.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…so Windows builds are cacheable at last (ADR-0076)

The ccache log named the refused option: -clang:-MD, with -clang:-MT and -clang:-MF. CMake Windows-Clang platform module sets those for clang-cl under Ninja on purpose, preferring the gcc-style depfile over /showIncludes, and ccache 4.14.1 made ccache reject every /clang: pass-through option by design. They meet on every translation unit, so every Windows compile was uncacheable (0 of 149) and the jobs compiled cold on every run.

When ccache drives clang-cl, the root CMakeLists now sets the dependency flags to /showIncludes with the msvc depfile format, the values CMake Windows-MSVC module uses for cl, which ccache supports. The log-file diagnostics leave the workflow; the verbose stats stay as the measurement. ADR-0076 records the cause, the three earlier commits that assumed the cache worked without reading its stats, and the guardrails.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ls cacheable, 135 MB and 197 MB saved

The 16 calls still refused are the C files of mimalloc, a FetchContent subproject whose own project() call re-applies the platform defaults in its directory. Every b2 translation unit is cached.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…so the Windows builds are cacheable at last (bertiniteam#524)

Both Windows jobs run clang-cl through ccache, and for as long as the
stats step has existed every call has been uncacheable (`Cacheable
calls: 0 / 149`, `Unsupported compiler option: 149 / 149`): the saved
cache was 13 KB and the C++ test job compiled cold on every run, 32
minutes. The three earlier commits around Windows ccache (87bb4c6,
b4884c7, 0d4d2b0) never read that number.

This PR first made the stats verbose and then had ccache write its log,
which named the refused option: `-clang:-MD`, with `-clang:-MT<obj>` and
`-clang:-MF<depfile>`. CMake's `Platform/Windows-Clang.cmake` sets those
for clang-cl under Ninja on purpose ("Prefer clang-cl's gcc-style
depfile over cl-style /showIncludes"), and ccache 4.14.1 (2026-09-27)
made ccache reject every `/clang:` pass-through option by design; 4.14.2
keeps that. They meet on every translation unit. Precompiled headers and
debug information were not involved.

The fix: when ccache drives clang-cl, the root `CMakeLists.txt` sets
`CMAKE_DEPFILE_FLAGS_<lang>` to `/showIncludes` and the depfile format
to `msvc`, the values CMake's own Windows-MSVC module uses for cl, which
ccache supports (it replays the include notes on a hit, so Ninja's
dependency parsing still works). The jobs keep
`CCACHE_SLOPPINESS=pch_defines,time_macros` (two targets use a
precompiled header) and `CCACHE_COMPILERTYPE=clang-cl`, and print
`ccache --show-stats -v`. ADR-0076 records the cause and the guardrails:
do not revert the override for CMake's preference, do not switch ccache
off as a precaution, read `Cacheable calls`.

This run's Windows jobs should show every call cacheable as misses and a
cache save in the tens of megabytes; the first run after that shows
hits.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
A computation written out between its stages must continue with the draws an
uninterrupted run would make, and b2 could set a seed but not get or set the
state the draws had reached.

RandomState holds everything the calling thread's next draws depend on: the
global seed, whether one was chosen, the solve-seed ordinal and the b2rand/1
draw stream.  GetRandomState and SetRandomState get and set it; its fields are
public and states compare with ==.  ToText and FromText write and read it as
versioned plaintext, b2randstate/1, and Boost archives carry that text, so a
state can go through MPI.  In Python: bertini.random.RandomState,
get_random_state and set_random_state; a state pickles as its text.

The thread's mt19937 engine and ThreadEngine are removed: every draw comes from
the draw stream (ADR-0044) and nothing drew from the engine, so no result
changes, and a second piece of random state was a trap for anyone saving it.

ADR-0075; a tutorial in the record-keeping section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every public class has a repr in bertini/_repr.py.  A state prints its global
seed and solve ordinal, or that no seed is chosen; a draw stream prints its
name, since its position is a key and a counter that to_text() gives.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unctions

The hand-written nibble arithmetic is replaced by boost::algorithm::hex_lower and unhex; what remains is the check that the text is exactly 32 bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_solve_seed

"Derive" read as a derivative.  The function advances the solve-seed chain one step and returns its new seed, so the call after it returns the next one.

The old names have shipped since 3.0.0, so they stay until 5.0: the C++ one marked [[deprecated]], the Python one warning with a DeprecationWarning.  The Python docstring said the function drew from the stream; it draws nothing, and now says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…c ADR on integration/intervals (bertiniteam#526), and 0076 by the clang-cl ccache ADR now on develop

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes bertiniteam#518.

A computation written out between its stages must continue, after a
restart, with the random draws an uninterrupted run would make. b2 could
set a seed and read it back, but not get the state the draws had
reached, nor set it.

### What changes

- **`RandomState`** (`random.hpp`) holds everything the calling thread's
next draws depend on: the global seed, whether one was chosen, the
ordinal the next `AdvanceSolveSeed` takes, and the thread's `b2rand/1`
draw stream. Its fields are public, and states compare with `==`.
- **`GetRandomState()` and `SetRandomState(state)`** get and set the
calling thread's state. The draws after setting a state are the draws
that followed getting it: tested for every kind of draw, a homotopy's
construction, and a solve.
- **Serialization at the edge.** `ToText()` and
`RandomState::FromText(text)` write and read versioned plaintext,
`b2randstate/1`; `FromText` refuses any other text with
`std::invalid_argument`. Boost.Serialization archives carry that text,
so a state goes through text and binary archives, and MPI. `DrawStream`
gains `ToText`, `FromText` and `==`.
- **Python:** `bertini.random.RandomState` (fields, `to_text`,
`from_text`, `==`, pickling as its text), `get_random_state()` and
`set_random_state(state)`.
- **`DeriveSolveSeed` is `AdvanceSolveSeed`** (Python
`advance_solve_seed`). "Derive" read as a derivative; the function
advances the solve-seed chain one step and returns its new seed. It has
shipped since 3.0.0, so the old names stay as deprecated aliases (C++
`[[deprecated]]`, Python `DeprecationWarning`) until 5.0. The Python
docstring said it drew from the stream; it draws nothing, and now says
so.
- **One source of randomness.** The thread's `std::mt19937` engine and
`ThreadEngine()` are removed. Every draw already came from the draw
stream (ADR-0044) and nothing drew from the engine, so no result
changes; it was a second piece of state that anyone saving the state
would have had to know about.

Per-path and per-worker streams are not part of the state: each is
reseeded from the global seed and its index when it is used (ADR-0071).

### Decisions

ADR-0077 records them, among them two open questions: whether "the
calling thread only" is enough (a program drawing setup randomness on
several threads of its own has a state per thread), and whether a
solve's `ScopedSessionState`, which restores the draw stream and
precision but not the seed or the solve ordinal, should restore the
whole state. ADR-0044's line keeping the engine is annotated. (It is
0077 because 0075 is the interval arithmetic ADR on
`integration/intervals`, bertiniteam#526, and 0076 the clang-cl ccache ADR, bertiniteam#524.)

### Docs

A tutorial, *Continuing a computation with the same random draws*, in
the record-keeping section, and a CHANGELOG entry under 4.1.0.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
…tiniteam#521)

A program keeping solutions between runs lost what b2 knew about them:
SolutionMetaData could not be written out.  The cellular decomposition port
needs it to write its complete state between stages and continue a killed run
with exactly the data an uninterrupted run produces.

records/solution_metadata_json.hpp writes every field of SolutionMetaData, both
precisions, as b2meta/1 JSON with the track records' encodings (exact decimals,
multiprecision at its own precision, success codes by name), and reads it back
equal in every field, path_time_seconds included.  Another format or numeric
type, a missing field or one of the wrong kind is refused.  In Python the
metadata types gain to_json and from_json.

Fixed: pickling or copying a solution's metadata in Python went through the
writable fields, so singular_values and latest_path_point came back empty.  A
class that writes itself exactly now pickles and copies through that.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ead of compilers, whose Fortran compiler brought clang along with ~600 MB of LLVM tools and flang; clang is now 22 instead of 19

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…runners have git, and nothing asked for conda's copy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ller; b2 links no BLAS, and the Linux jobs already test against OpenBLAS (numpy's pip wheels)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the environment file and the month, not the workflow: ~0.5 GB in place of six unpacked folders of ~2.6 GB each, which evicted every other platform's caches; the wheel tests restore it too

Of a ~320 MB solve, ~40 MB depends on the Python version, so whichever job saves the cache first leaves the others a few packages to download.  Unpacked copies are left out of the cache: conda unpacks an archive it finds.  Leaving the workflow file out of the key means editing the workflow reuses the cache; the month in it brings conda-forge's updates in.  The wheel builds' pip cache held only build and delvewheel and is dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he environment asks for clang >=20, which Visual Studio 2026's STL accepts, the proper fix the override's comment named

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…kage archives, keyed by the environment file and the month

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…month: each job restores the newest cache for its Python, installs, records conda list --explicit, prunes to those archives, and saves under the record's hash when it is new

A cache that hits saves nothing, so a key that stays the same would make every run download each conda-forge release since the save; the month only bounded that.  The record changes exactly when the contents do.  One cache per Python version (~320 MB each) stores the shared packages six times, but needs no job ahead of the Windows jobs to solve all six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut Boost's auto-linking on Windows asked for boost_json.lib by name, and the link failed once the linker no longer searched conda's library folder

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the SDK's rc.exe and mt.exe, by full path, instead of whatever LLVM the runner image ships, which CMake took once conda's lld was gone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s/msvc_link_tools.ps1), and log what they were configured with, how the conda cache was used, and fail where the build fails

The conda activation does not run Visual Studio's setup script in PowerShell, so VCToolsInstallDir was unset and the previous run stopped before configuring.  tools/msvc_link_tools.ps1 takes link.exe and lib.exe from the default toolset of the newest Visual Studio, the one clang-cl finds, and rc.exe and mt.exe from the newest Windows SDK.  tools/conda_archive_cache.ps1 takes over the cache bookkeeping and reports, in every Windows job including the wheel tests, the archives reused from the cache, downloaded, and restored but unused, with the main package versions.  The build steps print clang-cl's version and the compiler and link tools in CMakeCache.txt, and now fail when the build does: a native command's exit code does not stop a pwsh step, so the first run's link failure surfaced only as ten tests Not Run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cache of the package archives saved no time, so it is gone, and the builds enter Visual Studio's developer environment so link.exe finds the Windows libraries

Over three runs, conda installs took 178 s and 211 s in the two jobs that restored a 326.7 MB cache and 116-252 s in the 16 that had none; conda rewrote all 81 restored archives, reusing none, and the most a working cache could save is a download about as fast as its own 5 s restore.  ADR-0078 records the numbers and the decision.  The previous run's link failed with LNK1104 kernel32.lib: link.exe needs LIB, which only Visual Studio's developer environment sets.  tools/msvc_link_tools.ps1 enters it, puts PATH back so the build keeps conda's cmake, ninja, ccache and clang-cl, and takes the four tools from the variables it sets.  The build steps print which cmake, ninja, clang-cl and ccache they run, and every Windows job prints the main conda package versions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e stack: test_nag_algorithms died with heap corruption (0xc0000374) under clang 22 and link.exe where it passed under clang 19 and lld-link

The first run of bertiniteam#527 that linked built everything and passed nine suites, and test_nag_algorithms exited with 0xc0000374 two seconds in, during the zero_dim tests, with no stack and its buffered stdout lost. The suites run in the C++ test job without the mimalloc hook (only the CLI and the Python module install it), so the corruption is in the C runtime heap itself.

The C++ test build now carries debug information (/Z7, which ccache can cache, and /DEBUG) and, when a suite fails, a step reruns each failed suite under cdb with Boost.Test naming every test case on stderr as it starts, under both thread settings, and lists the DLLs the binary imports. The package listing adds gmp, mpfr, mpc, libboost-headers and the two runtime packages.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…s a scoped variable, it adds a run under full page heap, and the C++ job saves ccache before the tests so a failed test does not cost a cold build

The previous run's crash step stopped at a parse error, $failedLog: in a double-quoted string, so cdb never ran. test_nag_algorithms crashed the same way again, 0xc0000374 about two seconds in, so the crash is deterministic. The failed job skipped actions/cache's post step and saved nothing, so restore and save are now separate steps and the save comes right after the build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cleanup ran after the thread's MPFR state was freed, which corrupted the heap when a worker exited (ADR-0079)

Run under cdb, test_nag_algorithms faulted in mpfr_bernoulli_freecache reading 0xfeeefeee, called from mpfr_free_cache2 in the executable's __dyn_tls_dtor during thread shutdown, and under full page heap in libgcc's _emutls_get_address. conda-forge's Windows MPFR is built by gcc and keeps its thread-local state in emulated TLS, which was already freed. On Windows, mpfr_extensions.hpp now specializes mpfr_cleanup<true>::force_instantiate to skip Boost's thread_local object, and WorkerThreadPool, the only place b2 starts threads, calls ReleaseThreadMpfrCaches last on every platform. thread_pool_workers_that_filled_mpfr_caches_exit_cleanly is the regression test.

The serial rerun found a separate bug, in the test the_accuracy_estimates_are_recorded_under_names_that_say_their_coordinates: earlier["accuracy_estimate"] = earlier.at(...) read a value through a reference the insert had invalidated. AddressSanitizer reports it as heap-use-after-free on Linux too. The test now copies the value first.

The C++ test build drops the /Z7 and /DEBUG flags that found this, and keeps the step that reruns a failed suite under cdb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tiniteam#522)

Closes bertiniteam#521.

A solution's metadata (`SolutionMetaData`, both precisions) could not be
written out and read back, so a program keeping solutions between runs
lost what b2 knew about them. The cellular decomposition port needs it
to write its complete state between stages and continue a killed run
with exactly the data an uninterrupted run produces (with bertiniteam#518, PR bertiniteam#520,
for the random state).

### What changes

- **`records/solution_metadata_json.hpp`**: `SolutionMetaDataToJson(m)`
and `SolutionMetaDataFromJson<ComplexT>(text)`, with
`EncodeSolutionMetaData` / `DecodeSolutionMetaData` on `boost::json`
objects. Every field is written, with the encodings of the track records
(`solver_recording.hpp`): doubles as exact decimals, multiprecision
values as full-precision decimals with their precision, success codes by
their fixed names (`SuccessCodeFromName` is the inverse of
`CanonicalName`). The text names its format, `b2meta/1`, and its numeric
type; a reader refuses another format, another numeric type, a missing
field, or a field of the wrong kind, with `std::invalid_argument`.
- **Python**: the metadata types gain `to_json()` and `from_json(text)`.
- **Fixed: pickling or copying a solution's metadata kept only the
fields that can be set.** The metadata classes are enhanced in
`bertini/config.py`, which pickled and copied through `to_dict` over the
writable fields, so `singular_values` and `latest_path_point` (read-only
properties) came back empty. A class that writes itself exactly
(`to_json` / `from_json`) now pickles and copies through that.

### Tests

C++ (`zero_dim_records.cpp`, suite `solution_metadata_json`): a solve's
metadata reads back `==` and with the same `path_time_seconds` (which
`==` leaves out), and re-encodes to the same text; a multiprecision
metadata with every field set away from its default, at 60 digits under
a default of 30, reads back equal and at its own precision; refusals.
Python: the same round trip for double and adaptive solves, pickle and
copy keeping every field, refusals.

### Docs

A section, *Keeping what the solver knew about a solution*, in the
all-solutions tutorial, and CHANGELOG entries under 4.1.0. The format is
versioned and self-describing, so a later representation (MaRDI's, when
b2 adopts it) can be derived from it or replace it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
…f it, and link with Visual Studio's tools (bertiniteam#527)

The repository's Actions caches were full: 8.9 GB of GitHub's 10 GB, all
on develop, and not one Linux cache. The Windows jobs each cached their
whole conda package folder, about 2.6 GB apiece, one for the C++ tests
and one per Python version. Six of those don't fit, and while they
evicted each other they evicted every other platform's caches and every
pull request's, so PRs compiled cold.

This PR set out to cache the Windows environment better, measured
whether that pays, and found it doesn't: the Windows jobs now install a
slim environment and cache none of it. ADR-0078 records the numbers.

Moving the toolchain also exposed a crash on Windows that the old one
had hidden, which this PR fixes. ADR-0079 records it.

### What changes

- **The Windows environment is a third the size.** `environment-win.yml`
asks for `clang >=20`, which provides clang-cl, instead of the
`compilers` metapackage: its Fortran compiler (flang) brought clang
along with llvm-tools and lld, 830 MB of a 1153 MB download. It drops
conda's git (115 MB; the runners have git) and takes OpenBLAS instead of
MKL for numpy and scipy (109 MB; b2 links no BLAS). An install downloads
~325 MB.
- **No conda cache on Windows.** Over three runs of this PR, with the
archives cached and every job logging what conda reused: the two jobs
that restored a 326.7 MB cache (in 5 s) installed in 178 s and 211 s,
the 16 without one in 116-252 s, and conda rewrote all 81 restored
archives, reusing none. Runner speed decides the install time, and the
most a working cache could save is a download about as fast as its own
restore. The ~1.9 GB it would hold stays free for ccache and the other
platforms.
- **The STL version-gate override is gone.** Visual Studio 2026's STL
refuses Clang older than 20 (STL1000), and the conda clang was 19, so
the builds defined `_ALLOW_COMPILER_AND_STL_VERSION_MISMATCH`. The pin
is `clang >=20` (22.1.8 today), which compiled everything without it.
- **The builds link with Visual Studio's tools.** Without conda's lld,
CMake took `lld-link`, `llvm-rc` and `llvm-mt` from the runner's own
LLVM, which nothing pins. `tools/msvc_link_tools.ps1` enters the
developer environment of the newest Visual Studio (`vswhere`; the one
clang-cl uses) for `LIB`, `LIBPATH` and `INCLUDE`, puts PATH back so
conda's cmake, ninja, ccache and clang-cl stay in use, and returns
`link.exe`, `lib.exe`, `rc.exe` and `mt.exe`, which both builds pass to
CMake by full path.
- **`bertini2` defines `BOOST_JSON_NO_LIB`.** b2 uses Boost.JSON
header-only, but on Windows Boost's auto-linking asked for
`boost_json.lib` by name, and only the old linker happened to find one
in conda's library folder.
- **The Windows build steps log what they used and fail where they
fail**: main conda package versions, which cmake/ninja/clang-cl/ccache
run, `clang-cl --version`, and the compiler and link tools in
`CMakeCache.txt`. A native command's exit code does not stop a pwsh
step, so a failed link had surfaced only as ten tests "Not Run".
- **A thread frees its own MPFR caches (ADR-0079).** With the new
toolchain `test_nag_algorithms` died with heap corruption (0xc0000374).
Boost.Multiprecision frees MPFR's per-thread caches from a
`thread_local` destructor; conda-forge's Windows MPFR keeps that state
in libgcc's emulated TLS, and in an executable the destructor ran after
the state was freed, so a worker's exit read freed memory. The read does
not depend on the compiler; why it became fatal here is not established.
On Windows, `mpfr_extensions.hpp` now skips Boost's `thread_local`, and
`WorkerThreadPool` calls `ReleaseThreadMpfrCaches()` last on every
platform. `thread_pool_workers_that_filled_mpfr_caches_exit_cleanly` is
the regression test.
- **A test no longer reads freed memory.** `earlier["accuracy_estimate"]
= earlier.at(...)` in `zero_dim_records.cpp` read through a reference
the insert had invalidated. The debugger's heap made it fail, and
AddressSanitizer reports it on Linux too.
- **The Windows C++ job can show a crash and keeps its compile work when
a test fails.** When a suite fails, a step reruns it under cdb and then
under full page heap, naming each test case as it starts. ccache is
saved right after the build, because `actions/cache`'s post step is
skipped when a job fails.

The wheel jobs' pip cache, which held only `build` and `delvewheel`, is
dropped. The branch name records where this started.

### After merging

The old `windows-conda-*` caches on develop (~2.6 GB each) can be
deleted by hand or left to age out after 7 days unused.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
…FR thread-exit fix (bertiniteam#527), Windows ccache (bertiniteam#524), the random state (bertiniteam#520) and solution metadata JSON (bertiniteam#522)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	CHANGELOG.md
#	docs/adr/README.md
…he other main conda packages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ofloveandhate
ofloveandhate merged commit 2a3397c into bertiniteam:integration/intervals Nov 8, 2026
92 of 93 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant