Skip to content

Native structure and bound structs (PR-5) - #770

Draft
jtravs wants to merge 100 commits into
feature/two-backends-binary64from
refactor/native-structure-seam
Draft

jtravs wants to merge 100 commits into
feature/two-backends-binary64from
refactor/native-structure-seam

Conversation

@jtravs

@jtravs jtravs commented Nov 12, 2026

Copy link
Copy Markdown
Contributor

The first half of PR-5 of the lean-codebase plan: the native helpers, the bound problem struct and the workspace forwarders. Stacked on feature/two-backends-binary64, with chore/native-tooling merged in. It will be retargeted to main later. The split of general.cpp, close() by destruction, the decomposition of long functions and the dead-code removal (steps 4–7) come in a later PR.

What changed

  • Messages. refuse<Error>(parts...) and concat(parts...) (include/solprop_native/refuse.hpp) replace about 40 hand-built "..." + std::to_string(n) refusals. Every message keeps its exact text.
  • Build facts. include/solprop_native/build.hpp defines cuda, hip, device, host and openmp. Where both branches compile in every build, #if blocks become constants or if constexpr. Blocks that name device-only or host-only facilities keep their #if, and so does the Adams flow guard that nvcc needs.
  • Width dispatch. dispatch_among and dispatch_width (include/solprop_native/dispatch.hpp) replace the four switch ladders: the band filter, the modal projection lanes, and the device Adams prediction and element.
  • Forwarders. The private member template GeneralState::with_team(&GeneralWorkspace::f, args...) replaces the bodies of the 66 forwarders that opened the CPU team scope by hand. The declarations stay, because the pImpl keeps the workspace class out of the strict units.
  • Bound problem. native::Problem's 161 fields are nested in 14 groups: dims, grid, spatial, flux, stepper, linear, absorber, transform, kerr, plasma, raman, general, flow and dispersive. Each group is bound with nb::class_, and FieldFamily with nb::enum_.
    • Vector fields take only exact NumPy arrays (float64, complex128, uint64 or bool, C-contiguous). Fields whose count is laid out beside them also require a fixed rank. Anything else is refused, and the message names the field.
    • Problem::validate() holds problem_from's presence and extent rules, with the same messages.
    • The adapter's _lower(problem, request, plan) replaces _native_problem, its dict and its payload helpers.
    • Check: before the dict path was removed, a temporary comparison lowered 851 problems from the native test suites. Every field matched the old dict bit for bit. The problems covered plasma, envelope, Raman, general modal, coupled flow, dispersive, free, planar and low-storage cases.
    • Tests: they now set struct fields instead of dict keys. No assertion or match= was removed and no tolerance changed.

Hot-path commits (for the A/B)

  • 41f53f3c dispatch_width: the band filter (host and device), the modal projection lanes, and the device Adams prediction and element.
  • 9b6eec2f build facts: the Adams block width, the accepted-step fence and the band league. The others are cold predicates.
  • f7f5ddc2 with_team: every GeneralState call, trial_step and the Adams trials included, goes through std::invoke.
  • 2da322d9 bound problem: a mechanical rename of problem_.x to problem_.group.x throughout general.cpp, kernels included.
  • d8d3af10 refuse: the cold throw branches of the stage-table and z-table lookups and of prepare_trial.

Every commit is meant to give bitwise-identical results, field_difference 0.

Sizes

tools/size_report.py covers src/solprop, which this PR does not touch. For native/ (git diff --shortstat from the base: 40 files, +4839 / −4316):

before after
C++ (src, include, tests/native) 56,448 56,619
adapter Python 13,864 13,877
_core.pyi 715 946
native Python tests 13,567 13,675

The plan's savings are not reached yet. Most of them were to come from the part of step 2 that this PR leaves open (below).

Not done (step 2 remainder)

  • Binding the march, watch, Runge-Kutta, session, transverse and stage-table structs with nb::class_, in place of the *_from and *_to dict conversions. Their dicts are produced and read in solprop as well as in the adapter.
  • Splitting NB_MODULE into bind_state.cpp, bind_session.cpp and bind_build.cpp.
  • Turning the BoundState lambdas into .def(&BoundState::f, nb::call_guard<...>()). Most lambdas convert NumPy inputs before releasing the GIL, so they first need an exact-array argument type.
  • Generating _core.pyi with nanobind.stubgen, with a freshness check. While most of the seam is untyped dicts, a generated stub would type them as bare dict and break strict typing in the adapter. The problem's section of the stub was generated by stubgen, then written as plain attributes.

Verification (Mac, Apple Clang via Homebrew LLVM, editable Release build, OpenMP CPU)

  • Native pytest modules, run with -n 4: test_api_contract, test_backend, test_envelope_plasma, test_mixtures, test_execution, test_band_plasma, test_session_failure, test_dispersive, test_free, test_envelope_statistics_refusal, test_coupled_flow, test_general_modal, test_radial, test_planning, test_raman_tail and test_batch.
  • Native parity modules from tests/backends, also with -n 4: low storage, planar, far field, every statistic, dispersive Kerr, held plasma, node chunk, phi moments, z table, stage tables, march admission, rk/march traces, widths. Flow sessions and marches, resume history and helpers from tests/driver and tests/test_helpers.py ran too.
  • GoogleTest in a Debug build: passed.
  • ruff, the native pyright configuration (0 errors) and clang-format.
  • clang-tidy on the changed sources. One finding was fixed in its own commit.
  • Two local failures happen on this Mac without the change too, so they are not counted against it:
    • test_public_surface_is_exact: an uncommitted tree gives a .dYYYYMMDD version suffix.
    • test_response_column_boundary_preserves_full_and_tail_windows[19]: it fails the same way on main here.
  • Not run here: CUDA and HIP compiles. The dispatch lambdas and the if constexpr facts are compiled on devices for the first time in the device-compile lanes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HAYtF5P9QqEd3HQx3WJkA3

jtravs and others added 30 commits November 11, 2026 23:18
Remove SOLPROP_NATIVE_GEMM_PROVIDER and its CUSTOM, KOKKOSBLAS_NATIVE and
KOKKOSBLAS_TPL values, with the cuda-custom, cuda-native and cuda-tpl
presets that alone selected them. No shipped or CI build used them.
custom_gemm stays as the hybrid provider's kernel for ineligible shapes,
and the build record keeps gemm_provider (always HYBRID).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benchmark_gemm.cpp compared the GEMM providers that are gone; the
SOLPROP_NATIVE_BUILD_BENCHMARKS option built nothing else. End-to-end
'benchmarks ab' decides performance questions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No CI lane or shipped build uses icpx, and its special cases (the
precise floating-point model, its LTO archiver, the pthreads FFTW
requirement and MKL's Intel threading layer) cost about 45 CMake lines.
Linux CPU builds now take GCC 14 or Clang/AOCC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SOLPROP_NATIVE_ENABLE_THREADS had no C++ use, no CI lane and no shipped
build; every CPU wheel, Windows included, runs OpenMP. A CPU build's
execution space is now OpenMP only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Kokkos-FFT requires the single-precision FFTW libraries, but every
fftwf_ call it makes sits behind an if constexpr on float that the
double-only package never takes. Point FFTW_FLOAT_LIB and
FFTW_FLOAT_OPENMP_LIB at the double libraries before Kokkos-FFT is
configured, as the MKL branch already does, drop the single-precision
FFTW builds from the wheel recipes, and make the wheel smoke test refuse
a bundled libfftw3f or an fftwf_ import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
size_cliffs and statistics_cost become benchmarks.size_cliffs and
benchmarks.statistics_cost (statistics_cost builds ctc_reduced through
the registry's builder and keeps m2_reduced's own table). The other
native/benchmarks scripts (m1, m2, ctc, gemm_shapes, provider_sweep,
device_record) predate 'python -m benchmarks track/ab', which covers
their workloads, and nothing referenced them. cpu_timer.cpp, a Kokkos
Tools timer with no build rule, goes too: the coverage label module
counts and must not time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
check.sh hand-listed 40 files for clang-format and missed 43 of the 83
tracked ones; it now formats what 'git ls-files' lists under native/.
.clang-format adds InsertNewlineAtEOF, RemoveSemicolon and regrouped
include blocks (this package's headers, third-party libraries, the
standard library, platform headers; conditional includes stay put).
'poe fmt-native' formats the same list in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It removes the semicolon that ends a KOKKOS_LAMBDA variable's
declaration, which clang-format parses as a function definition.
Declaring the macro would fix that, but makes formatting general.cpp
take minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF4KqhYgzk6eBpqdfWA
Mechanical: clang-format 23.1.0 over the files the check now covers,
with the regrouped include blocks and a newline at the end of each file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF4KqhYgzk6eBpqdfWA
The dev group pins clang-tidy 23.1.4, the PyPI wheel nearest the
formatter's 23.1.0 (the wheels start at 23.1.3), and CMake looks for it
beside the build's Python before any machine's LLVM. CI's tidy lane
used Ubuntu 24.04's LLVM 18 and now uses the pinned one. On macOS the
SDK is passed explicitly, as Homebrew's Clang finds it from a
configuration file the PyPI build does not read.

'poe tidy-native [FILE...]' runs it on the given files, or on the C++
files that differ from main, with the cpu-tidy preset's compile
commands (configured when missing).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF4KqhYgzk6eBpqdfWA
The project's sources now also compile with -Wundef,
-Wnon-virtual-dtor and -Woverloaded-virtual; with
-Wimplicit-fallthrough under Clang; and with -Wduplicated-cond,
-Wduplicated-branches and -Wlogical-op under GCC. The list is defined
once and feeds the targets' options, the coverage label module and the
build record, which repeated it by hand. The one new finding (a test
helper's conditional with identical branches) is fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF4KqhYgzk6eBpqdfWA
The cpu-sanitize preset turns on Kokkos_ENABLE_DEBUG_BOUNDS_CHECK, and
sanitized targets compile with -fno-sanitize-recover=all, so a UBSan
report fails the test whatever UBSAN_OPTIONS says.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF4KqhYgzk6eBpqdfWA
native-deep.yml (weekly and by hand, report-only) collects Clang source
coverage over the GoogleTest and native pytest suites, runs the native
pytest suite on an extension built with ASan, UBSan and Kokkos bounds
checks (the bindings were never sanitized), and reports
misc-include-cleaner's findings. cuda.yml adds compute-sanitizer's
initcheck and synccheck beside memcheck and racecheck, and its comment
now names the GCC 14 host compiler CMake requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF4KqhYgzk6eBpqdfWA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF4KqhYgzk6eBpqdfWA
poe tidy-native and the include-cleaner report run clang-tidy after a
configure alone, which does not generate sleef.h.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF4KqhYgzk6eBpqdfWA
clang-analyzer-security.ArrayBound: Table::value wrote every column of
the table into its one double. Every caller reads a one-column table;
the guard makes that a refusal instead of an out-of-bounds write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF4KqhYgzk6eBpqdfWA
misc-redundant-expression: '1.0 / 1.0' is the same double as 1.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
misc-misplaced-const: 'const fftw_plan' makes the pointer const, not the
plan, which the spelling suggests. The handles keep their meaning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A reflowed NOLINTNEXTLINE would suppress its own continuation line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cppcoreguidelines-special-member-functions: AccelerateRamanPlan,
BoundState, BoundSession and the test's SynthesisCapture delete their
moves as they already deleted their copies (BoundState's address is
held by the runtime's list of live states). The coupled flow's local
RestoreFlow guard stays an aggregate, with a NOLINT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bugprone-unchecked-optional-access: the transverse edge's record and
held readings, the session's monitor plan and the bound marches'
policies and stage tables are set whenever these lines run, by
invariants the flow analysis does not follow (a validating lambda, an
earlier stage of a state machine, a refusal in another member). A
.value() read keeps them checked; it throws instead of reading an empty
optional. The configuration counts .value() as checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bugprone-throw-keyword-missing and bugprone-unused-raii read the
temporaries that register the exception types as exceptions never
thrown. Registration happens in the constructor either way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bugprone-unused-return-value: the pointer was taken with get() before
the capsule was made, so release() only gives up ownership.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.clang-tidy moves from an allow-list of about 35 checks to the bugprone,
performance, modernize, misc and readability families, the clang
analyser and cppcoreguidelines-special-member-functions, each exclusion
with its reason; warnings stay errors. The findings are fixed in the
commits that follow, one check each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs/dev/tooling.md gains the native C++ tooling table, each tool's
status and the reason for every rejection. native/AGENTS.md and the
native README name the public API as solprop_native.__all__, add
'poe fmt-native', 'poe tidy-native' and the weekly native-deep.yml, and
the changelog records the tooling change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
readability-use-concise-preprocessor-directives, applied by clang-tidy. Directives in inactive branches are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
readability-isolate-declaration, applied by clang-tidy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
modernize-use-nodiscard, applied by clang-tidy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
modernize-use-designated-initializers, applied by clang-tidy: each field is named where an aggregate is built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jtravs and others added 29 commits November 12, 2026 00:22
readability-implicit-bool-conversion, in two setup loops.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bugprone-integer-division: a plane node's row index (in the fluence-moment and second-moment kernels) and the rate tables' interval count are whole numbers by design; each is now an integer of its own before its conversion. The kernels' arithmetic is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bugprone-implicit-widening-of-multiplication-result: the envelope rule count and the packed radial inventory's limit were int products widened afterwards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bugprone-branch-clone: without a vendor BLAS the envelope branch and the non-real branch both returned false. The guard is now one #if'd test, and the family test its own; the decisions are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The readability-inconsistent-ifelse-braces fix opened a brace on two 'else' arms that end at an #endif, and closed it outside: the CUDA and HIP builds, which skip the host branch, saw an unmatched brace. The closing brace now sits under the same condition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
readability-suspicious-call-argument: a generic element functor, the FFT pool's lookup and the monitor's power callable take arguments whose names merely resemble other parameters'; each keeps a NOLINT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
readability-implicit-bool-conversion (reported by the Linux tidy lane): SpaceAccessibility<...>::accessible is an enumerator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On Linux it reports Kokkos's shared allocation records, freed through a
reference count it cannot follow, as a leak inside Kokkos's own header
when a view is constructed. The code holds no raw ownership, and the
sanitizer builds check for leaks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
misc-use-internal-linkage (the Linux tidy lane): source_digest() is
defined in the generated source_digest.cpp and declared and read in
bindings.cpp, so it needs external linkage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clang-tidy has no PyPI wheel for Windows on Arm, so the Windows arm64
test lanes built LLVM from its sdist and timed out. The native gate
that uses it is bash, run on Linux and macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benchmarks.statistics_cost built ctc_reduced through the registry's
capillary builder, which changed the timed workload: tolerances, loss,
temperature, seeded noise and n_save all differed from the deleted
native/benchmarks/ctc.py. It now builds that case's own table exactly
as the old harness did (rtol 1e-6, atol 1e-10, lossless, Raman,
n_save 21, the defaults otherwise), as m2_reduced already did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tidy.sh reconfigured only when the compile commands were missing, ran
whatever clang-tidy was on PATH, and copied check.sh's compiler choice
(without its /usr/local fallback). CMake now writes the clang-tidy
command it runs, the resolved binary first, to clang_tidy_command.txt;
tidy.sh reconfigures when that or the compile commands are missing or
older than CMakeLists.txt, runs that command, and refuses a clang-tidy
whose version is not the pinned one. The compiler and Raman provider
choice moves to scripts/compiler.sh, sourced by both scripts, and the
weekly include-cleaner report reads the same command file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The earlier guard threw from Table::value, a per-stage read. The
loader now checks every scalar table (density, taper, Kerr density,
chord, and each Raman, plasma and dispersive table) holds one value and
refuses one that does not by name; Table::value keeps a debug assert.
The test exercises the loader's refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
performance-unnecessary-value-param: the bindings' NOLINTBEGIN/END over whole files goes. The check's AllowedTypes now admits nanobind's reference-counted handles (nb::dict, nb::object, nb::handle, nb::ndarray and the bindings' ...Input and ...Array aliases of it), as it does Kokkos views; the one remaining finding, an optional dict copied into a bound lambda, now takes it by reference.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The block that names the double libraries for Kokkos-FFT's single-precision ones copied FindFFTW's search and dropped parts of it. It now makes one find_library per double library (fftw3, and fftw3_omp with OpenMP), with FFTW_ROOT or FFTWDIR as hints, honouring FFTW_USE_STATIC_LIBS, and points the FLOAT variable at each; FindFFTW keeps the cached results. The wheel smoke test still refuses an fftwf_ import.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
native-deep.yml's coverage job builds with Ubuntu's clang-18 and its
extension job with ASan, configurations no pull-request lane compiles;
with warnings as errors a new diagnostic there would stop the report.
Both now build with SOLPROP_NATIVE_WARNINGS_AS_ERRORS=OFF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
clang-tidy 23.1.4 joins the dev group's record beside clang-format
23.1.0, with why it is pinned and why only on Linux and macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
workflow_dispatch reaches only workflows on the default branch, so a
change to this report-only workflow could not be tried before merging.
A pull request that edits the file now runs it; it still gates nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main's lint now caps positional parameters at five (PLR0917).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Python loads no C++ runtime, so ASan, preloaded alone, could not
resolve __cxa_throw at start-up and aborted at the first C++ exception
the extension threw. libstdc++ is now preloaded beside it, and the
report-only step records its outcome in the job summary instead of
failing the job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
refuse<Error>(parts...) and concat(parts...) (include/solprop_native/
refuse.hpp) replace the hand-built `"..." + std::to_string(n) + "..."`
messages. Numbers keep std::to_string's text, so every message is
byte-identical. The march's and the watch's own policy helpers are named
refuse_policy so that they no longer shadow it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF5P9QqEd3HQx3WJkA3
include/solprop_native/build.hpp holds cuda, hip, device, host and
openmp as inline constexpr bools. The #if blocks whose branches both
compile in every build become constants or if constexpr: the crossing's
device test, the real-signal switch, the register plane, the host pack
layout, the Adams block width, the accepted-step fence, the band league
and four host-only eligibility predicates. Blocks that name a device- or
host-only facility keep their #if.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF5P9QqEd3HQx3WJkA3
dispatch_among<Values...> and dispatch_width<First, Last>
(include/solprop_native/dispatch.hpp) turn a run-time extent into a
compile-time one through a forced-inline comparison chain, which the
compilers fold back into the switch. They replace the band filter's
half-width ladder, the modal projection's lane ladder and the device
Adams prediction's and element's width ladders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF5P9QqEd3HQx3WJkA3
A private member template, GeneralState::with_team(function, arguments...),
opens the workspace's CPU team scope and invokes the workspace's member
on it; the 66 forwarders that opened the scope by hand become one call
each. It is defined and instantiated in general.cpp alone, so the pImpl
stays the firewall that keeps the workspace class from the strict units.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF5P9QqEd3HQx3WJkA3
clang-tidy's readability-avoid-return-with-void-value: the three void
overloads no longer return their member's void, and with_team, which
returns whatever its member returns, says why it may.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF5P9QqEd3HQx3WJkA3
native::Problem's 161 fields are grouped into sub-aggregates (dims, grid,
spatial, flux, stepper, linear, absorber, transform, kerr, plasma, raman,
general, flow, dispersive), every access renamed mechanically. The
extension binds them with nb::class_ (FieldFamily with nb::enum_): a
scalar field reads and writes as itself, a vector field as an exact NumPy
array, float64, complex128, uint64 or bool, C-contiguous, of a declared
rank where its count is laid out beside it, refused otherwise with the
field's name. Problem::validate() keeps problem_from's presence and extent
rules and their messages, and the CPU execution and FFT planning records
still cross as dicts.

The adapter's _lower(problem, request, plan) fills the structs in place of
_native_problem's dict and its payload helpers; a lowering of 851 problems
from the native test suites matched the old dict field for field, bit for
bit, before the old path was removed. Tests address the struct fields
instead of dict keys, with every assertion and match kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF5P9QqEd3HQx3WJkA3
The bound struct's fields read and write the same types, so the stub
declares each as an attribute instead of a property and its setter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HAYtF5P9QqEd3HQx3WJkA3
@read-the-docs-community

Copy link
Copy Markdown

This branch has not been deployed

No deployments
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