Skip to content

Variation / Morris Sensitivity Suite: consolidate 34 draft PRs (#4142, #4433, #4120, #4206) - #4447

Open
dieterolson wants to merge 334 commits into
mainfrom
consolidated/variation-morris-2026-08-13
Open

Variation / Morris Sensitivity Suite: consolidate 34 draft PRs (#4142, #4433, #4120, #4206)#4447
dieterolson wants to merge 334 commits into
mainfrom
consolidated/variation-morris-2026-08-13

Conversation

@dieterolson

@dieterolson dieterolson commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

Consolidates the variation / Morris-sensitivity draft family (34 open draft PRs) into one branch against main.

Known non-required-lane state, disclosed rather than left to be discovered: tests (3.12) reports 6 failed / 4893 passed. Three are main's own pre-existing failures, fixed in #4480 (main fails them today at edfeae684; see #4481). The other three are this family's, fail only on 3.12, and pass on 3.11 — filed as #4482 (an over-tight cross-client parity fixture, the Morris authority child never reporting readiness, and a JSON error taxonomy that shifts with the interpreter). tests (3.12) is not a required check; none of the six is fixed here, per the rule that a consolidation is the wrong vehicle for pre-existing defects.

Correctness findings are now filed as issues — they outlive this consolidation and are not fixed here, because a consolidation is the wrong vehicle for pre-existing defects:
#4455 (Morris validation is scale-blind — highest value) · #4456 (variation_parity.json vacuous) · #4457 (three flight-model policies) · #4458 (parity-only tests) · #4459 (tautological router verification) · #4460 (tests (3.11) never created on any of the 34 drafts) · #4461 (unpinned Python/TS duplicates) · #4462 (suite cannot detect a planted-design substitution)

Supersedes

Every PR below has been verified by ancestry check to be a true ancestor of this branch, so closing it drops nothing. Verified with verify_coverage.sh: covered=34 NOT-covered=0 SAFE, and independently against each PR's current headRefOid from the API (not from a cached branch table).

Supersedes #4393, Supersedes #4394, Supersedes #4395, Supersedes #4396, Supersedes #4397, Supersedes #4398, Supersedes #4399, Supersedes #4400, Supersedes #4401, Supersedes #4403, Supersedes #4404, Supersedes #4405, Supersedes #4407, Supersedes #4412, Supersedes #4413, Supersedes #4414, Supersedes #4415, Supersedes #4416, Supersedes #4417, Supersedes #4422, Supersedes #4423, Supersedes #4424, Supersedes #4425, Supersedes #4426, Supersedes #4427, Supersedes #4428, Supersedes #4431, Supersedes #4432, Supersedes #4435, Supersedes #4440, Supersedes #4441, Supersedes #4442, Supersedes #4443, Supersedes #4444

Epics

This family closes no leaf issues. It advances epics only:

Part of #4142
Part of #4433
Part of #4120
Part of #4206

(#4206 arrives via #4417, which had absorbed the already-merged #4436.)

Why merging the 7 chain tips was not sufficient

The brief assumed a chain tip contains everything below it. That is false here. #4417's branch head advanced after its six children branched off it: it now carries 89563be74 + 87719a266 (Sasho rotational AoA) via a merge of #4436, which is itself already MERGED into that feature branch and exists on no other branch and not on main. Those two commits are absent from all seven tips. They are merged here explicitly, so #4417 can be closed without stranding #4436's work.

#4424 and #4423 reported Already up to date — they were genuinely contained by the other tips. Confirmed by ancestry, not by assumption.

Blocking defects fixed

  1. Unreachable gitlinks (the repo-wide blocker). Removed the six mode-160000 entries under .codex-worktrees/. They are not declared submodules (no .gitmodules anywhere) and the commits they point at exist only in one local clone, on no remote. main has zero of them. Verified: git ls-tree -r HEAD --name-only | grep '^\.codex-worktrees' is empty, and no 160000 mode remains anywhere in the tree. The .gitignore line that prevents recurrence is owned by the CI/infra consolidation, not this branch.

  2. quality-gate Format Check would have failed on 91 files. Slices in this stack reformatted files with an older ruff, reverting them to the pre-0.9 wrapping style (assert hasattr(\n a, b\n), "msg" instead of assert hasattr(a, b), (\n "msg"\n)). I confirmed origin/main is format-clean under the CI-pinned ruff 0.14.10 while this branch was not, so the regression is the family's. Only 1 of the 91 files is rate_of_closure/variation code — the other 90 are collateral churn in unrelated trees. Reformatted with 0.14.10. The CI-excluded prefixes were deliberately left untouched.

  3. ruff check would have failed with 9 × F841. A slice wrapped nine app = QtWidgets.QApplication… statements across three lines in tests/unit/sidekick/test_sidekick_ux_hardening.py, moving # noqa: F841 off the line ruff reports the diagnostic on. Restored main's single-line form.

  4. tests (3.10) would have failed at collection. tests/unit/sidekick/agent/test_action_audit.py had been rewritten to from datetime import UTC, which does not exist on Python 3.10 — a regression of the fix SPEC 1.1.352 recorded for this exact file. Restored main's timezone.utc form. Root cause worth a follow-up: the repo's AST guard tests/rate_of_closure/test_python_310_datetime_boundaries.py only walks src/rate_of_closure, so it structurally cannot catch this class of regression in tests/ or in any other src tree.

  5. No raw conflict markers: git grep "<<<<<<< HEAD" is empty.

  6. quality-gate Type Check (Mypy) failed with 70 errors in 27 files — and every one of those 27 files is a file that does not exist on main at all. 428 of the 537 changed source files here are new. Because each source PR was based on its parent's branch, its changed-file delta only ever contained its own slice; once a file was introduced in slice N, no later slice re-checked it, and no PR in the family ever type-checked it against main. That is why all 34 sources show a green Mypy step while the consolidation did not. Fixed all 70 — 16 numpy no-any-return/var-annotated (annotated locals), 16 arg-type in _kinetics_dynamics.py (explicit float() at scalar-indexing boundaries), 16 arg-type from dict[str, object] inference being **-unpacked into typed constructors, 5 attr-defined/index in launcher.py (root cause: the unannotated GUI_INFO literal), 4 redundant-cast, plus one each of has-type, Literal narrowing, FrozenJsonValue, and an untyped Qt currentData(). All behaviour-preserving; the one public signature I briefly tightened was reverted in favour of a call-site fix.

  7. quality-gate Changed Test Assertion Check failed on six src/shared/python/swing_sim/**/tests/__init__.py package markers (five docstring-only, one empty). Added them to scripts/test_assertion_allowlist.txt, following the check's own guidance and the existing src/movement_optimizer/tests/** precedent.

  8. pyproject.toml and tool_manifest.yaml had been converted from LF to CRLF by this family, so both rendered as near-total rewrites. Restored LF: their real change is 35 added lines, not 802 changed ones. These were the only two files in the 1249-file diff with a newly introduced CRLF conversion.

  9. The web build was broken, and a real behavioural regression was hiding behind it. npm run build (i.e. tsc) failed, which is why "Rate web production Worker E2E" could not even start its preview server — that check passes on all four source PRs that carry it, so the breakage is the integration, not any one slice. Two distinct cross-slice defects:

    (a) Type error. feat(variation): bind runtime replay identity #4431 made executionMetadata required on VariationExecutionRequest/VariationExecutionResult, but four test fixtures introduced by the feat(rate): add bounded club explorer camera #4444 chain construct those objects without it. Supplied it the way the already-updated fixtures in the same file do — from the request under test, which also satisfies validateResult's request/result metadata equality check.

    (b) A user-visible regression, and the most consequential thing I found. importPlan reports provenance after loading a plan — either "resolves against the current variable registry … not evidence of historical reproducibility" for a raw plan, or "Execution sidecar verified against the current registry" for a signed document. The feat(rate): add bounded club explorer camera #4444 chain added an effect that calls invalidateResults on every configuration-identity change; loading a plan changes that identity, so the effect ran after commit and overwrote the provenance message with the generic "Ready: configuration changed; run again." The user was silently denied the one message that tells them a loaded plan was re-resolved against today's registry rather than its recorded one — an honesty regression in a suite whose whole theme is honest provenance. invalidateResults now announces only a discard that actually happened, decided from refs so the callback identity stays stable and the effect cannot re-trigger itself. Nothing else references that string, and the invalidation tests assert visual state and heading removal rather than status text.

    Two React tests (from feat(variation): bind runtime replay identity #4431's side) correctly detected (b) and were failing; they now pass. Web workspace: tsc clean, eslint --max-warnings 0 clean, vite build succeeds, vitest 1084/1084 across 126 files (was 1082/1084).

  10. The family's Python test suite had never run in CI at all, and once run it revealed five more cross-slice contract contradictions. pick-runner never granted any of the 34 drafts a fleet slot, so tests (3.11) was never even created on a single one of them — every "green" source PR was green on quality-gate alone. I built a Python 3.11 environment with CI's pinned numpy/scipy/pandas/PyQt6 and ran the suite. Five real failures, none caused by the merge; each is one slice's contract rejecting another slice's code, where no branch contained both halves:

    (a) The RNG-replay contract vs the explicit-design seam — the most serious. feat(rate-of-closure): produce paired localized attribution #4426 added build_simulation_ensemble_request_from_samples for "deterministic experimental designs whose rows are the scientific authority … rather than pseudorandom Monte Carlo draws". feat(variation): bind resolved execution metadata #4428/feat(variation): bind runtime replay identity #4431 then made SimulationEnsembleRequest require unconditionally that sampled_inputs equal sample_inputs(plan). Together the request contract rejects exactly the designs the seam exists to carry: 7 tests failed and the entire paired-attribution producer was non-functional. Reconciled by making the provenance explicit — sample_provenance defaults to plan_rng so every existing caller keeps the reproducibility guarantee bit-for-bit, and the explicit seam declares explicit_design, which skips only the RNG-equality check. Shape, finiteness, and _require_config_sample_binding — the check that actually proves each row was executed as written — all still apply. The same contradiction also broke a benchmark script and two archive tests.

    (b) A run may no longer be attributed to a config it did not come from. capture_simulation gained a config-identity guard; a Morris test injected pre-computed runs built from other configs. The guard is correct, so the test changed: the hit-availability case now asserts against project_simulation_outcome, which owns that rule, and the miss goes through the real evaluator end to end.

    (c) NaN where a null belongs. launch_monitor_import returned NaN for an empty CSV cell, because from pandas 3 a bare DataFrame construction coerces None in an object column. The test's own name says this import mirrors browser policy, which yields null. Built with dtype=object so an absent value stays absent instead of silently becoming a floating-point quantity.

    (d) Eight paired-study controls had no tooltips, failing the repo-wide tooltip-completeness contract that a different slice owns.

    (e) A request-identity test asserted identity by mutating sampled inputs in place, which is now unconstructable twice over. The sample-bytes case rebinds each config to its row; the config-order case now asserts the stronger property the binding provides — a misordered request is rejected outright rather than merely hashing differently.

    Result: tests/rate_of_closure/ is 1451 passed, 0 failed on Python 3.11. One further failure reproduced only on 3.13 (a JSON nesting-depth error-taxonomy difference) and passes on 3.11, so it is untouched.

Local reproduction note

Reproducing quality-gate on Windows requires MYPYPATH=src;src/python/src with a semicolon. CI is Linux and uses :; on Windows that colon string parses as one nonexistent directory, every first-party import silently degrades to Any under --ignore-missing-imports, and mypy reports a fabricated result. I measured both on this branch's exact 418-file list: semicolon gives Success, colon gives 109 errors in 13 files — a different, bogus set. Cross-module resolution also requires passing all 418 files to a single invocation via mypy's @listfile form (not xargs, which splits on Windows), numpy <2.4, and mypy pinned to 1.13.0. My initial semicolon run reproduced CI exactly: the same 27 files and 70 errors.

Substantive conflict resolutions

Two conflicts were more than textual, and both were resolved against the incoming side.

request_builder.py — a mis-merge that would have silently destroyed the paired-attribution design. Git aligned #4431's Monte Carlo function body against the #4444 chain's new explicit-sample seam, so the incoming side inserted

samples = sample_inputs(plan)

inside build_simulation_ensemble_request_from_samples — the function whose whole contract is that its caller-supplied rows are the scientific authority (planted baseline/perturbed pairs). Taking that side would have replaced an explicit design matrix with pseudorandom draws while every test still passed. Rejected. Execution metadata is now supplied by the sampling caller and stays None on the explicit seam: the metadata carries an RNG replay identity, and rows never drawn from that stream cannot be replayed from it, so claiming it would be a false provenance assertion.

simulation_adapter.pyassert execution_metadata is not None became a None-tolerant identity override for the same reason. An injected executor still can never be reported as the pinned production implementation.

Remaining resolutions, all union-of-both-sides: attribution wiring re-homed from the eager _on_ensemble_succeeded into _apply_prepared_result so it publishes only for an accepted result (guarded by authority() is None per #4427, and not cleared by _clear_result_widgets, so explicit paired authority keeps its own lifecycle); EnsembleStreamHeader fields and validations; the TS execution-metadata contract (the prepared request now feeds both validateExecutionRequest and validateResult); #4425's flight-model check kept alongside the stricter isSafeInteger nRuns check; #4427's value-based scenario comparison (strictly narrower than the identity check it replaced); and the dated sections in SPEC/handoff docs.

Correctness findings — read before merging

These are defects in the consolidated work, not in the consolidation. None is a merge error. I am reporting them rather than fixing them because each is a design decision for the epic owner.

1. There is no TypeScript Morris implementation, despite a branch named codex/4142-morris-typescript-parity. The Morris numerics exist in exactly one place: src/shared/python/swing_sim/variation/global_sensitivity.py. The TypeScript side is a wire parser plus an algebraic invariant checker. src/rate_of_closure/variation/morris_rate_adapter.py and morris_execution.py contain zero effect arithmetic. So the recurring Python/TS algorithm divergence cannot occur here — but the "parity" framing overstates what exists, and there is no second implementation to cross-check against.

The TS check that does exist is genuine, not decorative: morrisMetricValidation.ts:39-63 enforces sigma² − n·SE² − c·muStar² + c·mu² ≈ 0 with c = n/(n−1), which is an exact identity given Python's ddof=1 estimators, and would fire if Python switched to ddof=0. Zero-clamp thresholds match exactly (64 * eps * max(1, muStar) on both sides). Its limitation is structural: it validates a relation among metrics, so a uniformly wrong scale factor passes.

2. The Morris estimator has exactly one genuine numeric validation, and it is scale-blind. test_global_sensitivity.py:89-114 uses response 2·x₀ + 3·x₁ over bounds [0,1], so closed-form elementary effects are exactly 2 and 3. That is real ground truth. But every numeric mu_star assertion in the repo runs on factors with lower=0.0, upper=1.0, where normalized and physical coordinates coincide. Elementary effects are computed per normalized factor range (global_sensitivity.py:173 divides by signed_steps, which are in [0,1] units), and no test can currently distinguish "per normalized range" from "per physical unit." test_morris_rate_adapter.py does use non-unit bounds but contains zero mu_star assertions. This is the highest-value gap I found.

3. Parity-only tests (agreement asserted, correctness not). morrisUiParity.test.ts (both tests), test_morris_ui_contract.py::test_python_generated_ui_fixture_is_exact_and_shared, test_web_parity_fixture.py (both tests), variation.test.ts::"matches the fixture dispersion within the loose band", morrisGlobalSensitivityContract.test.ts::"parses the Python golden fixture…", morrisPresentation.test.ts::"ranks finite effects…" (its muStar 3 and 2 are read from the fixture), and the morris_workspace_v1.json consumers. One test is mislabeled as parity and checks nothing cross-runtime: morrisAuthorityRequest.test.ts::"matches Python R13.6 base-centered suggestions…" reads no Python artifact — its ten expected bound pairs are hardcoded TS literals, so Python drift cannot fail it.

To be fair to the fixtures: morris_ui_parity_v1.json and morris_global_sensitivity_golden_v1.json are genuinely two-sided (each is pinned by a Python test and a TS test, so a one-sided change fails exactly one), and no regeneration script exists for any fixture, so none is auto-derived from one side. The golden fixture's mu_star values coincide with the analytically-asserted 2.0/3.0, making it the one fixture transitively anchored to closed-form truth.

4. variation_parity.json is effectively vacuous as a correctness gate. The TS side accepts mean_abs_tolerance of 8.0 m on a 216.73 m mean (3.7%) and std_rel_tolerance of 0.4 (40%). A browser engine with a 39% dispersion error passes. The Python test's own docstring concedes exact RNG parity is not attempted.

5. #4425's flight-model pin contradicts the Morris slices in this same stack. Three policies now coexist:

Surface Allowed flight models
Python VariationPlan (spec.py:283) all 7 FlightModelType members
variationSchema.ts validatePlan 1 (waterloo_penner)
morrisAuthorityRequest.ts AUTHORITY_FLIGHT_MODELS all 7

A plan with flight_model in {macdonald_hanzely, nathan, ballantyne, jcole, rospie_dl, charry_l3} is valid in Python, round-trips through to_json_dict, and is rejected by the browser — and accepted by the Morris path in that same browser. The reverse cannot happen. #4425's error message ("unsupported for browser variation") makes this honest rather than silent, and #4425's Python-side change is sound (it requires plan and config to agree, without restricting the set). But the browser is now internally inconsistent, and SUPPORTED_VARIATION_FLIGHT_MODEL has no test asserting the rejection or documenting the asymmetry.

6. normalized_step can be reported inaccurately and neither runtime can detect it. _validate_design_paths never requires |signed_steps| == delta, nor that points lie on the k/(levels−1) lattice. TS parseDesign re-derives levels/(2·(levels−1)) from levels — validating the field against itself, not against the divisor actually used. MorrisResults.tsx then displays that number to the user.

7. Smaller divergences, all currently latent but not guarded: the Morris workspace CSV export has 39 Python columns vs 35 TS, with different order and different names for the same data (source_time_window_start_s vs source_time_start_s, design_levels vs levels, …) and four Python-only columns — each side asserts only its own header, and nothing compares the two. OUTPUT_LABELS is duplicated (23 Python entries vs 17 TS) and already out of sync. variationRegistry.ts is a hand-mirror of registry.py, including the typicalScale values that drive every Morris suggested bound, with no test pinning the two together (I compared the 10 Morris entries by hand — they agree today; nothing keeps them agreeing). Presentation tie-breaks differ (Python codepoint spec_id vs TS localeCompare), as do fallback labels (str.title() vs first-char capitalization: "carry_2m""Carry 2M" vs "Carry 2m"). Finally, application/morris/router.py:238 "verifies" a result by calling analyze_morris on the same inputs and comparing — tautological; it can catch transport corruption but never an algorithm error.

What I could not verify

  • I ran the JavaScript suite but not the Python suite. The web workspace is fully exercised locally (vitest 1084/1084, tsc, eslint, vite build).
  • The Python suite now runs locally on 3.11 and is green (1451 passed), including the PyQt paths I re-homed. PyQt6 6.11 does import on this machine, so those tests executed rather than being reasoned about. What I did not run locally is the rest of the repo's suite outside tests/rate_of_closure/, the Rust wheel build, or the Playwright PyQt-DPI lane.
  • My decision to leave execution_metadata=None on the explicit-sample seam is a judgement call, not something a test forced. It is the honest reading (no RNG was used, so no RNG replay identity should be claimed) and it is consistent with the stack's own "model identity honesty" theme, but if the epic owner intends explicit designs to carry metadata, it needs a non-RNG provenance variant rather than reusing make_execution_metadata.
  • Four files under src/data_processing/ are changed by this family and are not ruff-format-clean under 0.14.10. They sit under a CI-excluded prefix, so the gate does not see them, and I left them untouched per the exclusion rule. Flagging so it is not mistaken for clean.
  • I reviewed the Morris and attribution numerics and the flight-model policy. I did not audit the club-explorer camera, Putting sample inspector, linked-scatter, or visualization-manifest slices from the Epic: Visual-first tab visibility and visualization-led React/PyQt UX #4433 series beyond conflict resolution.

Gates run locally

Reproduced with CI's own pinned tools, commands, and file lists:

  • ruff check (pinned 0.14.10), 596 in-scope changed files and full-repo: clean.
  • ruff format --check (pinned 0.14.10): clean.
  • mypy (pinned 1.13.0, CI's exact 418-file list and flags): Success, no issues.
  • scripts/check_test_assertions.py --changed-files: passed.
  • scripts/check_minimum_test_contract.py: passed.
  • scripts/check_module_size_budget.py --max-lines 1200 --include src: passed.
  • bandit -ll -ii over the 372 changed src files: 0 medium, 0 high.
  • git grep "<<<<<<< HEAD": empty. git ls-tree -r HEAD | grep '^160000': empty.
  • verify_coverage.sh: safe=34, NOT-covered=0, SAFE.

requirements.txt is untouched, so pip-audit is unchanged from main. file-size-budget is not a required check here, and I have not refactored any module to satisfy it.

Merge note

main requires linear history, so this must land via squash, not a merge commit. Squashing collapses 34 PRs' worth of slice history, so the provenance above is the only surviving record — please keep it in the squash message.

🤖 Generated with Claude Code

dieterolson and others added 30 commits August 6, 2026 02:41
# Conflicts:
#	SPEC.md
#	src/rate_of_closure/ui/pyqt6/simulation_view.py
…r-analytics

feat(rate-of-closure): add launch monitor analytics tabs
…y' into feat/4193-launch-direction-registry-integration
…ion-registry-integration

feat(rate-of-closure): add launch direction conventions
The web build was broken and two React tests failed once the whole family
was integrated. Both are genuine cross-slice defects that no individual PR
could expose, because each slice only ever ran against its own parent.

1. `npm run build` (tsc) failed, which is why "Rate web production Worker E2E"
   could not even start its preview server. #4431 made `executionMetadata`
   required on `VariationExecutionRequest`/`VariationExecutionResult`, but four
   test fixtures introduced by the #4444 chain construct those objects without
   it. Supplied it the way the already-updated fixtures in the same file do --
   from the request under test, which also satisfies `validateResult`'s
   request/result metadata equality check. For the two unsupported-flight-model
   cases the request is now spread from `jointRequest()` with only the plan
   overridden; `validateExecutionRequest` runs `validatePlan` before the
   metadata comparison, so the assertion still exercises the flight-model
   rejection it was written for.

2. A real behavioural regression. `importPlan` sets the plan, clears results,
   then reports provenance -- either "resolves against the current variable
   registry ... not evidence of historical reproducibility" for a raw plan, or
   "Execution sidecar verified against the current registry" for a signed
   document. The #4444 chain added an effect that calls `invalidateResults` on
   every configuration-identity change. Changing the plan changes that
   identity, so the effect ran after commit and overwrote the provenance
   message with the generic "Ready: configuration changed; run again." The
   user was silently denied the one message that tells them a loaded plan was
   re-resolved against today's registry rather than its recorded one.

   `invalidateResults` now announces only a discard that actually happened,
   decided from refs so the callback identity stays stable and the effect
   cannot re-trigger itself. Nothing else references that string, and the
   invalidation tests assert visual state and heading removal rather than
   status text, so they are unaffected -- they invalidate with results present,
   where the message still appears.

Verified in the web workspace: tsc clean, eslint clean (--max-warnings 0),
vite build succeeds, and vitest is 1084/1084 across 126 files (was 1082/1084).
@dieterolson
dieterolson disabled auto-merge August 14, 2026 06:09
@dieterolson
dieterolson enabled auto-merge (squash) August 14, 2026 06:09
codex-scheduled added 3 commits August 13, 2026 23:36
…esign seam

Two slices in this family carry contracts that are mutually exclusive as
written, and neither branch contained both, so both slices' tests passed in
isolation and the contradiction only appears once they are integrated:

- #4426 added `build_simulation_ensemble_request_from_samples`, whose entire
  purpose is deterministic experimental designs "whose rows are the scientific
  authority (for example, planted baseline/perturbation pairs), rather than
  pseudorandom Monte Carlo draws".
- #4428/#4431 made `SimulationEnsembleRequest.__post_init__` require
  unconditionally that `sampled_inputs` equal `sample_inputs(plan)` -- the
  plan-derived RNG stream.

Together, the request contract rejects exactly the designs the seam exists to
carry. Measured: 7 tests in test_localized_attribution_producer.py failed with
"sampled_inputs must exactly match the plan-derived RNG stream and order",
which is the whole paired-attribution producer being non-functional.

Reconciled by making the provenance explicit rather than implied.
`SimulationEnsembleRequest.sample_provenance` defaults to `plan_rng`, so every
existing caller and test keeps the reproducibility guarantee unchanged, and the
RNG-stream equality check is enforced exactly as before. The explicit seam
passes `explicit_design`, which skips only that one check; shape, finiteness,
and crucially `_require_config_sample_binding` -- the check that actually proves
each row was executed as written -- all still apply.

The Monte Carlo seam now passes `plan_rng` explicitly, because it delegates
through the explicit builder and must not inherit its default.

Verified with the pinned toolchain: the 7 producer tests pass, and 626 tests
across the variation, reference, flight, solver and impact suites pass with no
new failures. Confirmed the 5 unrelated failures in this area are pre-existing
on the pushed branch by re-running them with these two files reverted to HEAD.
The repo-wide tooltip-completeness contract (test_tooltips.py) failed on eight
widgets, all of them paired-attribution controls added by #4426/#4427: Cancel
Paired Study, the two export buttons, and the save/load authority buttons. The
tooltip contract comes from a different slice, so neither branch contained both
and the gap only appears once they are integrated.

Hints state what each control does and, where it matters, what it does not:
cancelling publishes nothing but retains prior authority, the view export
records what is displayed rather than the authority, and loading is accepted
only if the archived design and request identities still validate.
…ding contracts

The family's Python suite had never run in CI -- `pick-runner` never granted
any of the 34 drafts a fleet slot, so `tests (3.11)` was never even created on
them. Running it locally against CI's interpreter (3.11) and pinned dependency
set surfaced five real failures, none caused by the merge itself: each is a
place where one slice's contract rejects another slice's usage, and no branch
contained both halves.

All five are the same two contracts meeting code written before them:

- `scripts/benchmark_rate_ensemble_archive.py` builds a synthetic all-zero
  sample matrix, which the RNG-stream guard rejected. It now declares
  `explicit_design`. Its noise variable also moved from `face_angle_deg` to
  `impact_offset_toe_mm` because the per-row config binding can only verify
  trace-capable keys; the benchmark measures archive RSS scaling, not any
  particular variable.
- `test_variation_ensemble_archive.py` asserted request identity by mutating
  sampled inputs in place. That is now unconstructable twice over, so the
  sample-bytes case rebinds each config to the row it is executed with and
  declares `explicit_design`, and the config-order case asserts the stronger
  property the binding actually provides: a misordered request is rejected
  outright rather than merely hashing differently.
- `test_morris_rate_adapter.py` injected pre-computed runs built from other
  configs. `capture_simulation` now requires a run to carry the exact config it
  was asked for -- a correct safety property, so the test was what needed
  changing. Rate Morris execution also requires the double_pendulum source,
  which misses, so the hit-availability case is asserted against
  `project_simulation_outcome`, which owns that rule, while the miss goes
  through the real evaluator end to end.
- `launch_monitor_import.py` returned NaN where an empty CSV cell should be
  null. From pandas 3 a bare DataFrame construction coerces None in an object
  column to NaN; the test's own name says this import mirrors browser policy,
  which yields null, so the frame is now built with `dtype=object`. An absent
  value stays absent instead of silently becoming a floating-point quantity.
- The RSS test's remaining failure was a missing local `psutil`, not a defect.

One further failure, in `test_variation_ensemble_io_reader.py`, reproduced only
on Python 3.13 and passes on 3.11, so it is left alone; it is a JSON
nesting-depth error-taxonomy difference, not a defect in the required lane.

Verified: `tests/rate_of_closure/` is 1451 passed, 0 failed on Python 3.11 with
CI's pinned numpy/scipy/pandas/PyQt6, plus ruff check, ruff format, and mypy
clean over CI's exact file lists.
@dieterolson
dieterolson disabled auto-merge August 14, 2026 07:15
@dieterolson
dieterolson enabled auto-merge (squash) August 14, 2026 07:15
Only two conflicts, both additive appends:
- scripts/test_assertion_allowlist.txt: kept this branch's swing_sim package
  markers alongside CONS-A1's pdf_renamer conftest entry.
- SPEC.md change log: union of both dated blocks.

Verified LF line endings survived on both files.
codex-scheduled added 2 commits August 14, 2026 12:42
…mplete

`tests (3.11)` failed at COLLECTION, which aborts the whole run before any test
executes -- hence all three matrix lanes failing identically:

    ERROR collecting tests/rate_of_closure/test_morris_authority_child.py
    ModuleNotFoundError: No module named 'uvicorn'

Declared the dependency rather than restructuring the test or adding a skip
guard. A `pytest.importorskip` here would make the Morris authority tests
silently vanish in CI, which is the same "check that reports success while
checking nothing" pattern filed as #4460; fixing a red gate must not create
another one. It is also not an option here: the test monkeypatches
`child.uvicorn` directly, so the module attribute has to exist.

`uvicorn` is already declared in the `rate-morris-authority` and `all` extras in
pyproject.toml, but neither job installs those extras -- both install
`requirements-ci.txt` plus an explicit `fastapi python-multipart` line, which is
where `fastapi` is effectively declared for CI. Added `uvicorn` there, in both
the quality-gate and tests jobs, since both build the same environment.

Auditing the same class across the branch found one more, which would have
failed as soon as collection got past the first: `psutil` is imported at module
scope by `scripts/benchmark_rate_ensemble_archive.py`, which
`test_variation_ensemble_archive_memory.py` runs as a subprocess with the same
interpreter. It is declared in NO requirements file, pyproject extra, or
workflow anywhere in the repo, and the test has no guard, so a missing psutil
fails it rather than skipping. Declared alongside uvicorn.

Everything else the audit flagged is safe: `build123d` is guarded by
`pytest.importorskip` in both wedge tests, `swing_core` by try/except in
`_rust_facade.py`, `starlette` ships with fastapi, `mpl_toolkits` with
matplotlib, `jax`/`pendulum_core` live in the pendulum lane CI excludes from
changed-tests, and the rest are first-party packages.

Chose the workflow install line over `requirements.txt` deliberately: it matches
how fastapi, pymodbus, sqlmodel and httpx are already handled, and avoids making
`requirements-lock.txt` stale and widening pip-audit's scope.
Two conflicts, one of them a genuine divergent refactor of the same file.

**src/movement_optimizer/gui/motion_tabs.py** — both branches split this module
to satisfy the module-size budget, but differently. This branch extracted
`motion_helpers.py` (`build_motion_colors`, `chain_path_length`); #4438 extracted
`motion_view.py` (`MotionViewMixin`) and kept a local `_build_motion_colors`.
Took main's version wholesale and deleted the now-orphaned `motion_helpers.py`,
which nothing else imported. That reproduces main's exact, reviewed, green state
for a vendored sub-app that CI excludes from every ruff, mypy and changed-test
lane -- i.e. a place where a mis-resolution would not have been caught.

Worth recording: git presented the third hunk as main *deleting* the "Playback
speed" control, and it does not. Main keeps it at line 682 as a single-line
`_add_control` call while this branch had it line-wrapped. Both branches'
`test_motion_tabs.py` are byte-identical and both reference
`chain._controls["speed"]`, so dropping it would have broken the sub-app
silently. Verified before resolving rather than trusting the conflict markers.

**torque_utils.py** — rerere replayed this branch's resolution, which routes
polynomial evaluation through the shared `TorquePolynomial` instead of
`np.polyval` on reversed coefficients. Since the two use opposite coefficient
orders, I checked equivalence numerically rather than by reading: ascending
`TorquePolynomial(coeffs).evaluate(t)` matches `np.polyval(coeffs[::-1], t)` to
1e-12 across t in {0, 0.13, 0.5, 1.0, 2.5}.

Re-verified on the new base: mypy clean over CI's 418 files, ruff check and
format clean over 596, module size budget, changed-test assertion and minimum
test contract gates all pass, no conflict markers, no gitlinks, and CI's exact
210 changed-test files now collect cleanly -- 2572 tests, exit 0.
dieterolson pushed a commit that referenced this pull request Aug 14, 2026
rate_of_closure.web_companion.runtime and web_authority.child both do a plain
module-level 'import uvicorn', and tests/rate_of_closure imports them, so the
whole pytest lane aborts at collection without it. uvicorn was declared only in
pyproject's optional rate-of-closure-web extra, which CI does not install; CI
installs from requirements.txt. Same class as the filelock gap, and the same
failure that just took #4447 red.

Found by walking the import graph from tests/ through first-party modules and
diffing the unguarded external roots against requirements.txt plus the explicit
pip lines in ci-standard.yml. The only other new-in-this-branch hits were
starlette (a fastapi dependency CI installs) and mpl_toolkits (part of
matplotlib), both already satisfied.
codex-scheduled added 2 commits August 14, 2026 14:51
…4475)

`.github/workflows/ci-standard.yml` auto-merged cleanly -- #4475 changes the
`pick-runner` job's placement while this branch's change is in the dependency
install lines, so the two do not overlap. Verified both survived: the hosted
dispatcher routing is at line 83 and `uvicorn psutil` at lines 246 and 607.

This is a welcome merge rather than just a required one: #4475 moves
`pick-runner` off `d-sorg-fleet` onto a hosted runner for public repos, so the
dispatcher no longer burns a fleet slot purely to decide placement while every
job with `needs: pick-runner` queues behind it. That is precisely what has been
holding this PR's `tests (3.11)`.

One conflict, and it was not a content conflict.
`.github/workflows/cross-repo-python-integration.yml` came through as a
whole-file conflict because **main's copy is CRLF (256 lines) while this
branch's -- and the repo convention, `core.eol=lf` -- is LF (252 lines)**. Git
could not align a single line, so it presented both copies in full. Normalizing
for comparison shows the real change is 11 insertions and 7 deletions: #4475's
`CI_RUNNER_MODE`/`REPOSITORY_PRIVATE` plumbing, the public-repo hosted fallback,
and the removal of `vendor/ud-tools` from the sparse checkout.

Took main's version verbatim, CRLF included. Normalizing it to LF here would be
the correct convention but would add a 252-line line-ending diff to a
consolidation that does not otherwise touch this file, and the regression is
main's rather than this family's. Flagged for a separate one-line fix instead --
left as is, every other in-flight consolidation branch will hit this same
unalignable whole-file conflict.

Re-verified on the new base: mypy clean over 374 files, ruff check and format
clean over 584, assertion and module-size gates pass, both workflows parse as
valid YAML, no conflict markers, no gitlinks.
…step

I resolved the previous merge wrong. Taking main's
`cross-repo-python-integration.yml` wholesale to avoid a line-ending diff also
discarded two of this branch's own changes, because the file's content had
genuinely diverged and not only in #4475's hunks:

- `vendor/ud-tools` in UpstreamDrift's sparse-checkout scope
- the whole `Initialize pinned Tools submodule` step

This branch's `tests/ops/test_cross_repo_python_integration.py` asserts both
(it adds 27 lines main does not have, including a dedicated test that the
submodule step runs before dependency install), so I kept the tests while
dropping the thing they test. `tests (3.12)` caught it.

Rebuilt as the actual union: started from this branch's version and applied
#4475's two hunks (`CI_RUNNER_MODE || LOCAL_RUNNER_MODE` plus
`REPOSITORY_PRIVATE`, and the public-repo hosted-runner fallback). Written with
CRLF to match main so the diff against main is the 8 lines of real content
rather than a whole-file line-ending rewrite. Verified all three are present and
the submodule test passes again.

Two failures remain in this file's tests, and they are NOT from this branch:
`origin/main` fails the identical two. Proven by running them in a detached
worktree at edfeae6 -- `2 failed, 3 passed`. Main's own
REQUIRED_SPARSE_PATHS expects `shared`, `sidekick`, `python/src/utils`,
`contracts.py` while main's workflow declares the `src/`-prefixed forms. Main is
green only because this test file is not in main's changed-file set, so its
delta-CI never runs it -- the same blindness filed as #4460. This branch merely
touches the file and so inherits the failure.

Not fixing main's two here, per the standing rule that a consolidation is the
wrong vehicle for pre-existing defects. Reported for routing instead.
…nst recurrence

`tests (3.10)` aborted at COLLECTION with 18 errors, so the whole lane never ran
a single test. Two 3.11+ stdlib features were imported at module scope:

- `enum.StrEnum` in `club_camera.py` and `variation_visual_state.py` -- 17 of the
  18 errors, since every module transitively importing them failed too.
- `tomllib` in `tests/ops/test_rate_web_playwright_workflow.py` -- the 18th.

Both already have an established repo pattern that these bypassed. Of the 17
`StrEnum` imports in the tree, 15 are correctly version-guarded; only these two
were bare. This is the same defect class as the `from datetime import UTC`
regression fixed earlier on this branch: a 3.11+ feature taken directly instead
of through `shared.python.compatibility`.

Used the exact pattern the other modules use, after getting it wrong once:

    if TYPE_CHECKING:
        from enum import StrEnum
    else:
        from shared.python.compatibility import StrEnum

My first attempt imported the shim unconditionally, which fixed 3.10 but broke
Mypy -- 16 new errors, because the shim's `class StrEnum(str, Enum)` fallback
loses StrEnum member typing, so every `ClubCameraAction` and
`VariationVisualEvent` member degraded to `str`. The `TYPE_CHECKING` guard gives
the checker the real `enum.StrEnum` and the 3.10 runtime the shim, which is
precisely why the other modules are written that way.

`tomllib` uses the `try/except ImportError -> tomli` fallback already present in
three other test modules.

Also extended `test_python_310_datetime_boundaries.py`, which previously guarded
only `datetime.UTC`, with an AST check for module-scope `enum.StrEnum` plus a
self-test proving it accepts the guarded and shimmed forms and rejects only the
bare one. An unguarded import here is not a style question: it fails collection
and hides every other result on the lane.

Verified on a real Python 3.10.20 interpreter: `club_camera` imports and resolves
StrEnum from the shim. Mypy back to `Success: no issues found in 374 source
files`; 20 tests pass on 3.11; ruff check and format clean.

Left alone deliberately: `tests/test_pendulum_provider_manifest.py` and
`tests/video_analyzer/test_video_analyzer_packaging.py` also import `tomllib`
unguarded, but neither is in this branch's changed set. Fixing them would pull
them into CI's changed-test selection and could surface unrelated failures for no
benefit here.
dieterolson added a commit that referenced this pull request Aug 15, 2026
…ta CI (#4480)

Three tests were failing on `main` itself, invisible because Tools selects tests from changed files —
so `main`'s own delta CI never put these files in its changed set. Any branch that merely touched them
inherited the failures and appeared to have caused them.

- `test_each_downstream_declares_its_required_sparse_scope`
- `test_upstream_scope_includes_every_release_build_package_root`
- `test_refresh_user_functions_system_exit_propagates`

Fixes, per side-of-truth rather than to make the mirror agree:

- **Workflow was wrong -> fixed the workflow.** `src/chat`, `src/contracts.py`, `src/python/src/utils` and
  `src/sidekick` exist in neither form on UpstreamDrift's default branch (verified via the API, not a local
  clone). Removed rather than replaced with a guess — a test asserting a nonexistent path is a vacuous gate
  (see #4477).
- **Test expectation was stale -> fixed the test.** `REQUIRED_SPARSE_PATHS` becomes
  `{src/shared, tests/shared_contracts, tests/support}`, and the release-build-roots assertion becomes
  `{"src/shared"} <= scope`. Deliberately NOT bare `src`: the test asserts `"src" not in upstream_scope`,
  so narrowness is a design constraint, not drift. Declaring `src` would have satisfied the mirror while
  violating an explicit assertion.
- **`raise SystemExit(1)`** replaces `import sys; sys.exit(1)` in the rotation-converter test. The scripting
  sandbox now blocks `import sys`, so the old form never reached `sys.exit` and `SystemExit` was never
  raised — a stale test defeated by a correct security change.

Verified: 15 passed across both files on 3.11 (was 3 failed); ruff check and format clean; assertion gate
passes; YAML valid; all three files keep their existing line endings, so the CRLF workflow file shows a
content-only diff (see #4479 for the underlying `.gitattributes` gap).

Reviewer context: the Cross-Repo workflow's last *completed* run on `main` was a failure (2026-08-06).
This PR makes the *declaration* honest; it does not claim the job is green end to end. `vendor/ud-tools`
and the submodule-init step are owned by #4447 and deliberately excluded. `tests/fixtures` does exist and
is seeded onto `sys.path` by UpstreamDrift's vendoring test — flagged as a possible further gap, not folded
in here.

Unblocks #4447 and #4446, which inherited these failures by touching the same files.
Filed as #4481.
codex-scheduled added 2 commits August 14, 2026 19:20
#4480 landed the three fixes for tests that failed on `main` but were never
selected by its delta CI. Re-syncing clears the last known cause of this
branch's `tests (3.11)` failure.

One conflict, in the release-build-package-root assertion, resolved as the
union: #4480's corrected `{"src/shared"}` plus this branch's
`vendor/ud-tools`, which is the pinned Tools gitlink UpstreamDrift's own
test_tools_vendoring.py resolves against.

I checked the auto-merged regions on content rather than trusting them, since
this is the exact file pair I mis-resolved two merges ago. Both are correct:
the workflow's UpstreamDrift scope is #4480's cleaned list plus
`vendor/ud-tools` with the submodule-init step intact, and
REQUIRED_SPARSE_PATHS carries the same four entries -- so the mirror and the
workflow agree, which is the property the test exists to enforce.

Verified: the three formerly-failing tests now pass (16 passed on 3.11), mypy
clean over CI's 374 files, ruff check and format clean over 584, changed-test
assertion and module-size gates pass, no conflict markers, no gitlinks.
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