feat(swing-sim): land the ground skid/roll/bounce module as a standalone slice of #4466 - #4517
Merged
Merged
Conversation
…one slice `consolidated/rate-closure-remainder-2026-08-13` (#4466) cannot be merged: after #4473 squash-merged, every overlapping file is a both-added conflict with no common ancestor, and neither resolution direction yields a working tree (`-X theirs` → 47 failed / 40 errors; `-X ours` → 19 collection errors). Its unique work is separable, so it lands feature-first instead. `swing_sim.ground` is the cleanest slice. Its three dependencies outside the package — `flight/result_metrics`, `solver/spatial_targets`, `solver/target_serialization` — are already byte-identical on `main`, so this adds the 92-file module, `swing_sim/canonical_numeric_json.py`, and the ten shared Python/TypeScript ground golden fixtures, and changes nothing else. Six ground tests fail on the source branch itself, verified by running them there unmerged — #4466 has armed auto-merge and could never have gone green: - the four `test_skid_roll_passivity` cases build `SurfaceRun` without `active_surface`, which arrived with regional-surface support. Every one raised `TypeError` in its fixture, so none had ever asserted a passivity property. Fixed to pass `request.surface`, matching `simulate_skid_roll`. - `test_bounce_cancellation_is_typed_and_retains_request_identity` hardcoded the request's separation time and a zero elapsed span. `RepeatedBounceResult` requires the termination to match the final trajectory point and the elapsed span to match bounce chronology, so it raised before reaching the assertions it exists for. Both are now derived from the prefix. - `ground/__init__.py` eagerly imported `to_ground_model_result`, publishing the explicitly unqualified compatibility adapter on the package despite its deliberate absence from `__all__` and the lazy-import map — exactly what `test_unqualified_compatibility_adapter_is_not_public` guards. The name is unused inside `__init__.py`; the import is dropped. 321 ground tests pass. Scoped ruff, ruff format and mypy clean; the tools manifest layout check passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
dieterolson
enabled auto-merge (squash)
August 17, 2026 05:12
dieterolson
pushed a commit
that referenced
this pull request
Aug 17, 2026
…e-base wall Repo policy is to update the handoff with every PR, and two things changed since #4514 landed. The 39 codex/4142-* / codex/4433-* drafts are now closed as superseded by merged #4473, which says so in its own description. Open PRs went 58 -> 21 and drafts 39 -> 0. Their work is verifiably on main: the Morris chain as application/morris/{host,client,contracts}.py, the #4433 chain as the flight, putting, plot, visual-state and tab-audit modules. Their branches are left intact, so the guidance is now reopen-rather-than-rebase — those branches have diverged far enough that merging one would remove current main content. #4466 turns out not to be mergeable at all. After #4473 squash-merged, the shared merge-base collapses to a commit predating src/rate_of_closure/, making every overlapping file a both-added conflict with no common ancestor (281 of them). #4517 is landing it as standalone slices instead, starting with swing_sim.ground. The other consolidations should expect the same wall, so the roadmap now says slice rather than merge. Also added a do-not against per-file Python version guards, pointing at the conftest floor guard that replaces them. Handoff remains within the 150-line policy at 144. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ort module Two CI-only failures on the ground slice, neither reproducible on the local Python 3.13 run that reported 321 passing. `tests (3.11)` aborted during collection: `ModuleNotFoundError: No module named 'jsonschema'`. `test_profile_schema.py` and `test_schema_migration.py` validate the ground profile schema with `Draft202012Validator`. It is a test-only dependency — no `swing_sim` source imports it — and it happened to be installed locally, which is why the gap only showed in CI. Declared in `requirements.txt` rather than wrapped in `pytest.importorskip`: skipping would leave the profile JSON Schema contract unguarded in CI, which is the opposite of what these tests are for. `quality-gate`'s Changed Test Assertion Check flagged `ground/tests/_support.py` as a changed test file with no behavioral assertion. That is correct and intended — it builds shared requests and results, and every assertion about what it produces lives in the modules that consume it. Added to `scripts/test_assertion_allowlist.txt`, the mechanism the check names in its own failure message, next to the existing `swing_sim` entries. Verified locally: `scripts/check_test_assertions.py` passes against this branch's changed-file list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dieterolson
pushed a commit
that referenced
this pull request
Aug 17, 2026
…e-base wall Repo policy is to update the handoff with every PR, and two things changed since #4514 landed. The 39 codex/4142-* / codex/4433-* drafts are now closed as superseded by merged #4473, which says so in its own description. Open PRs went 58 -> 21 and drafts 39 -> 0. Their work is verifiably on main: the Morris chain as application/morris/{host,client,contracts}.py, the #4433 chain as the flight, putting, plot, visual-state and tab-audit modules. Their branches are left intact, so the guidance is now reopen-rather-than-rebase — those branches have diverged far enough that merging one would remove current main content. #4466 turns out not to be mergeable at all. After #4473 squash-merged, the shared merge-base collapses to a commit predating src/rate_of_closure/, making every overlapping file a both-added conflict with no common ancestor (281 of them). #4517 is landing it as standalone slices instead, starting with swing_sim.ground. The other consolidations should expect the same wall, so the roadmap now says slice rather than merge. Also added a do-not against per-file Python version guards, pointing at the conftest floor guard that replaces them. Handoff remains within the 150-line policy at 144. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dieterolson
pushed a commit
that referenced
this pull request
Aug 17, 2026
…e-base wall Repo policy is to update the handoff with every PR, and two things changed since #4514 landed. The 39 codex/4142-* / codex/4433-* drafts are now closed as superseded by merged #4473, which says so in its own description. Open PRs went 58 -> 21 and drafts 39 -> 0. Their work is verifiably on main: the Morris chain as application/morris/{host,client,contracts}.py, the #4433 chain as the flight, putting, plot, visual-state and tab-audit modules. Their branches are left intact, so the guidance is now reopen-rather-than-rebase — those branches have diverged far enough that merging one would remove current main content. #4466 turns out not to be mergeable at all. After #4473 squash-merged, the shared merge-base collapses to a commit predating src/rate_of_closure/, making every overlapping file a both-added conflict with no common ancestor (281 of them). #4517 is landing it as standalone slices instead, starting with swing_sim.ground. The other consolidations should expect the same wall, so the roadmap now says slice rather than merge. Also added a do-not against per-file Python version guards, pointing at the conftest floor guard that replaces them. Handoff remains within the 150-line policy at 144. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
|
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
… at It (#4516) * fix(ci, tests): enforce the two-tier Python floor instead of guessing at it The `tests (3.10)` lane was running code that requires 3.11, which produced failures that looked like defects and were not. This repo is deliberately two-tier. The root distribution declares requires-python = ">=3.11" (classifiers 3.11/3.12, mypy python_version 3.11), while ten sub-packages and Rust crates declare >=3.10 and ship 3.10 wheels from six maturin workflows. The 3.10 matrix lane exists to validate those sub-packages. But it ran the whole suite, including root-package code such as src/p1am_control_system and src/shared/python. Two failures came from exactly that mismatch: - test_deployment_hardening.py imported tomllib, stdlib only on 3.11+, aborting collection for the entire session (1,218 collected, zero executed). - test_estop_shutdown_safe_state.py timed out because asyncio.wait_for was reimplemented on asyncio.timeouts in 3.11 with different cancellation propagation. Neither is a 3.10 support bug. Both are 3.11-only code being run on 3.10. conftest.py now resolves the nearest pyproject.toml for any collection path, reads its declared requires-python floor, and skips collection when the running interpreter is below it. The floor is read from the declarations rather than hardcoded, so adding a sub-package or moving a floor needs no edit here. requires-python is regex-parsed on purpose: tomllib does not exist on the very interpreter the guard has to run on. It fails closed at (3, 11) when nothing parses. Verified as a strict no-op on 3.11+ — collection is identical at 10,382/10,414 with and without the guard. CLAUDE.md advertised a flat "Python 3.10+" that the root distribution rejects. It now states the real contract, names the ten 3.10 distributions, explains what the 3.10 lane is for, and says not to add per-file version guards because the conftest handles it. tests/test_python_version_contract.py locks the declarations together: requires-python against the mypy target and the classifiers, the CI matrix against the lowest declared floor, CLAUDE.md against the root floor, and a regression guard proving root-package tests are excluded below the floor while 3.10 sub-packages are still collected. The interpreter version is faked so those guards run on every lane, not only the one that cannot execute them. This supersedes the separate investigation into the e-stop shutdown timeout: that test should not run on 3.10 at all, and no shutdown-safety code needed to change. The pytest.importorskip added to test_deployment_hardening.py in #4515 is now redundant but retained deliberately as defense in depth — if this guard ever regresses, a bare import there takes down the whole session again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(agent-handoff): record the closed draft chain and the #4466 merge-base wall Repo policy is to update the handoff with every PR, and two things changed since #4514 landed. The 39 codex/4142-* / codex/4433-* drafts are now closed as superseded by merged #4473, which says so in its own description. Open PRs went 58 -> 21 and drafts 39 -> 0. Their work is verifiably on main: the Morris chain as application/morris/{host,client,contracts}.py, the #4433 chain as the flight, putting, plot, visual-state and tab-audit modules. Their branches are left intact, so the guidance is now reopen-rather-than-rebase — those branches have diverged far enough that merging one would remove current main content. #4466 turns out not to be mergeable at all. After #4473 squash-merged, the shared merge-base collapses to a commit predating src/rate_of_closure/, making every overlapping file a both-added conflict with no common ancestor (281 of them). #4517 is landing it as standalone slices instead, starting with swing_sim.ground. The other consolidations should expect the same wall, so the roadmap now says slice rather than merge. Also added a do-not against per-file Python version guards, pointing at the conftest floor guard that replaces them. Handoff remains within the 150-line policy at 144. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tests): check each sub-package at its own floor, not the global minimum The 3.11 lane failed on the previous commit with "src/movement_optimizer declares 3.10 support but the guard would exclude it from the 3.10 lane". The guard was not the problem; the test was wrong by construction. test_sub_package_tests_still_run_on_the_lower_lane faked the interpreter to the *minimum* floor across every lower-floored package, then asserted that every one of them would still be collected. That only holds when they all declare the same floor. A single package declaring lower than the rest makes the assertion fail for packages that are being excluded entirely correctly — a 3.10 package is supposed to be excluded on 3.9. Each package is now checked at its own declared floor, which is the property actually worth guarding and is independent of what anything else declares. Two related changes: _below_declared_floor now takes the interpreter version as an optional parameter instead of the tests patching sys.version_info. That patch was process-global and read by unrelated library code, which under xdist meant a test could perturb workers running beside it. test_conftest_reads_each_package_declared_floor now checks every sub-package rather than two hand-picked ones, and reports all mismatches with their paths. Path resolution is the part most likely to differ across platforms, so a failure should name the package instead of surfacing as a confusing assertion further downstream. Also added test_nested_distributions_do_not_widen_their_parent_tree. The diagnostic turned up a real wrinkle: psa_package declares >=3.10 while living inside src/shared/python, which is root-package code. The guard handles it correctly — the nested distribution gets 3.10 and its parent tree stays at 3.11 — but nothing was pinning that, and a regression there would quietly let the lower lane start collecting root code again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: allow empty collection on lanes below the root Python floor The floor guard works, and that turned out to be the problem for this PR's own 3.10 lane: pytest exited 5, "no tests collected". On a sub-floor lane the guard excludes root-package code, including the top-level tests/ tree. A change that touches only root-package tests therefore has nothing to run there, and pytest treats an empty selection as an error. The lane failed while behaving exactly as designed. The tests step now tolerates exit code 5, but only on lanes below the root requires-python floor, which is read from pyproject.toml rather than hardcoded so it tracks the declaration. On the required lane exit 5 still fails, which is what keeps the issue #3324 vacuous-selection protection meaningful — that guard exists to catch typo'd or broken test selection, and blanket-tolerating exit 5 everywhere would defeat it. Verified before pushing, since this is a CI workflow change and the previous two rounds of surprises here were self-inflicted: - version comparison: 3.9 and 3.10 allow empty collection, 3.11 and 3.12 do not - tolerance: exit 5 returns 0 when allowed, and passes through unchanged when not - the workflow YAML parses and the extracted run block passes bash -n Deliberately avoided a python heredoc inside the indented YAML block; the floor is parsed with grep instead, because heredoc terminators in a YAML block scalar are sensitive to how the block is de-indented. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: drop the 3.10 lane from ci-standard instead of propping it up Replaces the exit-5 tolerance from the previous commit, which was treating a symptom. Two facts settle it. Every core_tests entry is root-package code — all of them are tests/** or src/shared/python/sidekick/tests/** — so once the floor guard correctly excludes root-package code, the 3.10 lane can never run a single core test. And 3.10 compatibility for the ten sub-packages that actually claim it is already proven elsewhere: each crate's maturin-*.yml runs a build + parity gate across 3.10/3.11/3.12 that builds the wheel, installs it, and asserts both that the extension imports and that the native backend is selected. So the lane was not testing 3.10 support. It was running root-package code on an interpreter that cannot execute it, and the only reason it ever looked meaningful was that a collection abort masked the emptiness. Tolerating empty collection would have institutionalised a lane that consumes a runner and reports nothing on every PR. ci-standard now runs ["3.11", "3.12"], starting at the root floor. The exit-5 tolerance is reverted, and the now-dead `!= "3.10"` sidekick guard is removed. Two contract tests replace the weaker matrix check: test_ci_matrix_starts_at_the_root_floor fails if a lane is ever added below the root floor, with an error that points at the maturin workflows as the correct home for lower interpreters. test_lower_floor_packages_keep_a_workflow_that_exercises_them fails if a sub-package declares a floor below the root and no workflow outside ci-standard actually runs that interpreter. This is the guard against the real risk in this change: dropping the lane must not leave a 3.10 claim with nothing behind it. It passes today, which is what makes the removal safe rather than assumed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tests): select sub-package pyprojects by git tracking, not a denylist The 3.11 lane failed on a vendored dependency: .cargo-home/registry/src/.../pyo3-0.24.2/pyo3-runtime/pyproject.toml declares >=3.7 but no workflow outside ci-standard runs that interpreter That file is pyo3's own packaging metadata sitting in the cargo registry cache. It is a third-party artifact, not a claim this repository makes, and the directory does not exist locally — it is materialised only on CI runners, which is why the test passed here and failed there. The helper was enumerating pyproject.toml by rglob and filtering with a directory denylist (.venv, node_modules, target, build, dist, .git). A denylist is unbounded: every new tool cache is another entry nobody adds until it breaks a build. .cargo-home was simply the first one to appear. Selection is now by `git ls-files`, which answers "is this ours?" directly and cannot drift as new caches appear. It returns exactly the eleven real sub-package pyprojects and nothing else. If git is unavailable the tests skip rather than silently degrading to a weaker check. This fixes all four tests that share the helper, not just the one that failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 17, 2026
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
) Third slice, after ground (#4517) and flight (#4518). Adds six club modules — assembly binding and its atomic file I/O, engineering sidecar, simulation adapter, STL export — and splits the plot catalog into `_catalog_entries`, `_catalog_scalar_entries`, `_catalog_series_entries` and `_catalog_entry_types`. The catalog split was verified entry-for-entry: 78 identifiers before, 78 after, none dropped. A catalog is data, so a lost entry would not appear in a public-symbol comparison — counting them was the only way to know the refactor was faithful. `plotting/render.py` and `plotting/spec.py` keep main's versions. The source branch predates the plot point-inspector and series-selection work already on main, so its copies are a revert, not an update: taking them failed 13 tests (`assert None is not None`, rendered-pixel counts far below threshold). Restoring main's two files dropped that to zero. Makes `rate_of_closure.club` lazily export `assembly_binding`, `engineering_sidecar` and `simulation_adapter`. All three reach `shared.python.golf_club`, which transitively pulls `swing_sim.variation -> solver -> flight -> scipy.integrate`. Importing them at `__init__` scope meant `rate_of_closure.club.types` — a leaf module of frozen club specs — dragged SciPy in, because Python runs the package `__init__` first. That broke `test_ui_contract_submodules_import_without_optional_servers_ or_scipy`, which requires the Morris UI contract to import with scipy, fastapi and uvicorn blocked. The lazy map follows the shape `swing_sim.ground.__init__` already uses; all 23 lazy names and all 45 `__all__` entries still resolve. Verified: 2,538 tests pass across `tests/rate_of_closure` and `src/shared/python/swing_sim`. The two remaining failures are pre-existing and Windows-only, both confirmed against a clean tree — `test_text_reader_normalizes_decoder_resource_errors` (reproduces with these changes stashed, passes on Linux CI) and `test_new_plot_data_clears_selection` (a 15 s Qt budget that measures 13.7 s on this box, passes in isolation). Scoped ruff and ruff format clean. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
Fourth slice, after ground (#4517), flight (#4518) and club/plotting (#4519). Adds 20 modules: capability observation, Morris host/child adapters, regional ground variation and its control surface, scalar ensemble contract/IO/wire, and the wind-strategy plot adapter. Net-new files only. For the 16 files the branch also modifies, main is a superset and the branch copies are much older — `ensemble_chunks.py` -360, `_ensemble_parser.py` -340, `plot_definition.py` -312, `confidence_ellipsoid_mesh.py` -296. Symbol comparison confirms the direction: `from_json_dict`, `read_plot_definition`, `build_dispersion_metric_variability` and `apply_global_simulation_values` exist only on main. Taking those files would have reverted shipped work, which is the same trap that `plotting/render.py` sprang in #4519 and `test_wind.py` in #4518. `simulation_adapter.py` genuinely diverges — `run_simulation_ensemble_chunks` on main against `_TRIAL_FAILURES` on the branch — so it stays at main's version pending its own reconciliation rather than being guessed at here. Two `no-any-return` findings from the changed-file MyPy gate are fixed at the boundary where `--follow-imports=skip` degrades imported types to `Any`: `capability_observation_ensemble_json` converts explicitly, and `_spin_axis` unpacks the three components instead of returning the attribute, which also pins the arity its annotation promises. Verified: 2,632 tests pass across `tests/rate_of_closure` and `src/shared/python/swing_sim`. Both changed files type-check clean; ruff and ruff format clean across the package. The remaining local failures are the known Windows-only ones already confirmed against a clean tree. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
Fifth slice. Adds `ground_playback`, `ground_playback_comparison` and the three `ground_playback_workspace` modules under `src/rate_of_closure/simulation/`. This one was genuinely blocked, not merely unscheduled: the modules import `swing_sim.ground` (#4517), `swing_sim.flight` (#4518) and `rate_of_closure.club.{assembly_binding,simulation_adapter}` (#4519). With all three on `main` they now import cleanly — verified by importing each of the five directly, not only by the suite passing. Net-new files only, as with every slice. Of the six files the branch also modifies, `sources.py` is the only branch-superset (`world_from_selected_head`) and the new modules do not reference it, so every modified file stays at main's version rather than being taken on the strength of one symbol. One `no-any-return` finding from the changed-file MyPy gate is fixed where `--follow-imports=skip` degrades `GroundSimulationResult` to `Any`: `ground_result_json` converts explicitly, its exact-type precondition already guaranteeing the runtime value. Verified: 2,635 tests pass across `tests/rate_of_closure` and `src/shared/python/swing_sim`; all five new modules import; the Morris UI contract still imports with scipy, fastapi and uvicorn blocked — the regression #4519 had to fix in `club/__init__.py` — and the changed-test assertion gate passes. Scoped ruff, ruff format and per-file mypy clean. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
dieterolson
added a commit
that referenced
this pull request
Aug 17, 2026
…main (#4523) `rate_of_closure.variation.regional_ground_study_adapter` cannot be imported on current `main`: ImportError: cannot import name 'to_ground_model_result' from 'shared.python.swing_sim.ground' Two of my own PRs combined to cause it. #4517 removed that name from the ground package's eager exports, correctly — the package's own `test_unqualified_compatibility_adapter_is_not_public` requires the explicitly *unqualified* compatibility adapter to stay private, and it is absent from both `__all__` and the lazy-import map. #4520 then landed a consumer importing it from the package path. Neither PR's tests imported the adapter module, so both were green while three modules could not be loaded at all: `regional_ground_study_adapter`, `regional_ground_variation`, and `regional_ground_target_projection`. The import now names the owning module, `shared.python.swing_sim.ground.result_adapter`, which does export it. That keeps the package contract intact — the unqualified adapter is still not package-public — while making the unqualified dependency explicit where it is used, which is the honest thing for a compatibility path. Adds `tests/rate_of_closure/test_variation_module_importability.py`, which imports every module in the package. This is the check that was missing: a suite can be entirely green while a module in it is unreachable, and no existing gate covers that. It also catches the neighbouring cases — a symbol dropped from a package's `__all__`, a rename, a new circular import. Verified by reverting the one-line fix: 3 failed, 35 passed. The ground contract test still passes, so this does not weaken what #4517 established. Scoped ruff and ruff format clean. Co-authored-by: codex-scheduled <codex-scheduled@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lands
swing_sim.ground(92 files) fromconsolidated/rate-closure-remainder-2026-08-13as a self-contained slice against currentmain.Why a slice and not a merge
#4466 cannot be merged. After #4473 squash-merged, the shared merge-base collapses to a commit where
src/rate_of_closure/did not exist, so every overlapping file is a both-added conflict with no common ancestor — 281 of them, zero trivially identical. Neither resolution direction produces a working tree:The two snapshots have divergent, mutually-coupled API generations. The unique work is separable, so it lands feature-first.
Why ground is the right first slice
Its only dependencies outside the package —
flight/result_metrics,solver/spatial_targets,solver/target_serialization— are already byte-identical on main. So this adds the module,swing_sim/canonical_numeric_json.py, and the ten shared Python/TypeScript ground golden fixtures, and changes nothing else.Six broken tests repaired
All six fail on #4466's branch itself, verified unmerged. That PR has armed auto-merge and could never have gone green.
test_skid_roll_passivitybuiltSurfaceRunwithoutactive_surface(added with regional-surface support). Every case raisedTypeErrorin its fixture, so none had ever asserted a passivity property. Now passesrequest.surface, matchingsimulate_skid_roll.test_bounce_cancellation_is_typed_and_retains_request_identityhardcoded a termination time and zero elapsed span that violateRepeatedBounceResult's chronology invariants. Both are now derived from the prefix.ground/__init__.pyeagerly importedto_ground_model_result, leaking the explicitly unqualified compatibility adapter onto the package namespace despite its deliberate absence from__all__and the lazy-import map — precisely whattest_unqualified_compatibility_adapter_is_not_publicguards. Unused there; dropped.Verification
ruff check,ruff format --check,mypyclean on the changed files (60 source files)scripts/check_tools_manifest_layout.pypassesPart of epic #4103.
🤖 Generated with Claude Code