Skip to content

feat(python): MONO_LIB_PATH wrapper routing (#208 Phase 2c PR-A)#212

Merged
k-yoshimi merged 9 commits into
developfrom
l7b-ii-phase-2c-pra
May 25, 2026
Merged

feat(python): MONO_LIB_PATH wrapper routing (#208 Phase 2c PR-A)#212
k-yoshimi merged 9 commits into
developfrom
l7b-ii-phase-2c-pra

Conversation

@k-yoshimi
Copy link
Copy Markdown
Owner

@k-yoshimi k-yoshimi commented May 25, 2026

Summary

Closes #208 PR-A milestone (PR-B follow-up in a separate PR).

Adds a single-authoritative routing rule: setting
MONO_LIB_PATH=/path/to/libtotapi_mono.so makes every wrapper
(eqlib/trlib/fplib/tilib/wrxlib/totlib) inside or outside
TotPipeline load the same mono image. Plumbing prerequisite for
PR-B's _MONO_ONLY_RULES = {("eq","tr"): [...]} behavioral
activation.

What's in this PR (9 commits)

  1. feat(python): MONO_LIB_PATH helper + cache_clear test — new python/_runtime_mode.py with mono_lib_path() (lru_cache + strict verify).
  2. test(totlib): cleanup + precise pytestmark comment — minor follow-up on the helper test.
  3. feat(eqlib): consume MONO_LIB_PATH helper as priority 0 — wire eqlib + 3 wrapper-flow tests.
  4. feat(wrappers): wire 5 remaining wrappers — roll out to tr/fp/ti/wrx/totlib; tests parametrized over all 6.
  5. test(totlib): restore FileNotFoundError message assertion — Codex code-quality LOW fix.
  6. test(totlib): complete 9-case mono routing contract — 5 more spec §8.1 cases (per-module env, non-mono, libc lacks-symbol, empty-file dlopen, explicit lib_path).
  7. test(totlib): close handle in finally + libobjc removal note — Codex code-quality MED fixes.
  8. docs(tot+totlib): document MONO_LIB_PATH routingtot/tot_api.h + python/totlib/README.md.
  9. ci(python-tests): mono routing sanity stepmono build (Linux libtotapi_mono.so) job extended.

Acceptance (spec §11)

  • python/_runtime_mode.py matches §6 contract.
  • All 6 _ffi.py implement priority-0 hook per §7 (top-level import + mono_lib_path() before existing 4-step resolution).
  • python/totlib/tests/test_mono_routing.py has 9 cases per §8.1 (1 helper + 3 parametrized + 5 standalone).
  • CI mono-build job runs new test step with MONO_LIB_PATH and TOTLIB_PATH.
  • _MONO_ONLY_RULES untouched — no behavioral activation (PR-B scope).
  • 1e-10 equivalence preserved (totlib/tests/test_equivalence.py not modified, all equivalence tests still pass).
  • Both in-house and Codex pre-push reviews HIGH/MED-free.

Tests

  • New python/totlib/tests/test_mono_routing.py: 9 tests, all pass locally + via pytest --forked. Portable Linux + macOS.
  • Existing tests unaffected: 164/165 totlib pass (the 1 fail is the pre-existing Python 3.10 ExceptionGroup compat issue in test_close_raises_exception_group_when_multiple_modules_fail, same as PR fix(tot): cascade eq_api_init in tot_init (#209) #211 baseline — unrelated to PR-A).
  • Per-module suites (eqlib/trlib/fplib/tilib/wrxlib/totlib): no regressions introduced.

Pre-existing concern (separate triage item, NOT introduced by this PR): fplib/tests/test_equivalence.py and wrxlib/tests/test_equivalence.py each have 2 failures on develop baseline. Verified by checkout + run on origin/develop @ da91ee45. Should be filed as a follow-up issue.

Out of scope

  • _MONO_ONLY_RULES populated with ("eq","tr") rule — PR-B.
  • TotPipeline.run_pipeline([("eq",...),("tr",...)]) integration test — PR-B.
  • Cross-module coupling rules beyond ("eq","tr").
  • Fortran source restructuring (feedback_fortran_refactor_needs_lead_signoff.md).
  • Renaming <MOD>LIB_PATH env vars.

Spec + Plan

  • Spec: docs/superpowers/specs/2026-05-23-l7b-ii-phase-2c-pra-loader-contract-design.md (3 Codex passes, SHIP IT).
  • Plan: docs/superpowers/plans/2026-05-25-l7b-ii-phase-2c-pra-implementation.md (4 Codex passes, SHIP IT).

Process notes

Plan executed via subagent-driven-development skill: 6 implementer subagents (Tasks 1-6) + 2-stage review per task (spec + code quality). Task 5 implementer subagent accidentally committed CI yaml to local develop instead of the worktree branch — cherry-picked to the right branch + reset local develop to origin/develop before push. This mirrors the feedback_codex_worktree_sharing.md incident from a prior session.

Test plan

  • CI green on python-tests.yml (3.11 + 3.13 + mono build with new routing step)
  • Bugbot completes with no remaining HIGH/MED
  • PR-B (rule activation) can branch from this PR's HEAD

🤖 Generated with Claude Code


Note

Medium Risk
Changes default native library resolution for every Python wrapper when MONO_LIB_PATH is set; misconfiguration could load the wrong .so, though validation and forked tests reduce that risk. No behavioral coupling rules are enabled yet.

Overview
Adds MONO_LIB_PATH as a single routing switch so eqlib, trlib, fplib, tilib, wrxlib, and totlib can all load libtotapi_mono.so (one BPSD broker) instead of separate per-module .so files.

A new python/_runtime_mode.py exposes mono_lib_path(): it validates the path, dlopens the library, and requires tot_is_mono() == 1, with clear errors for missing files, bad dlopen, or non-mono images. Each wrapper’s _default_lib_path() consults that helper first; explicit load_library(path=...) / constructor lib_path still overrides.

test_mono_routing.py (forked pytest) locks the contract across all six modules; the mono-build CI job runs it. Docs note that eq→tr behavioral coupling stays for PR-B—this PR is loader plumbing only.

Reviewed by Cursor Bugbot for commit 951c56c. Bugbot is set up for automated code reviews on this repo. Configure here.

k-yoshimi and others added 9 commits May 25, 2026 10:46
Adds python/_runtime_mode.py with mono_lib_path() — the single
authoritative reader for the MONO_LIB_PATH env var. Strict + verify
per spec D-4: missing file -> FileNotFoundError; failed dlopen ->
RuntimeError; missing tot_is_mono symbol -> RuntimeError;
tot_is_mono() != 1 -> RuntimeError. lru_cache'd; cache_clear()
exposed for runtime env mutation (tests).

First test case from spec §8.1: test_helper_cache_clear pins the
cache + cache_clear contract so future refactors cannot silently
regress R-1.

Wrappers in subsequent commits will consume this helper as
priority-0 in their _default_lib_path() chains.

Spec: docs/superpowers/specs/2026-05-23-l7b-ii-phase-2c-pra-loader-contract-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
eqlib._ffi._default_lib_path() now consults
python._runtime_mode.mono_lib_path() before its existing 4-step
resolution. If MONO_LIB_PATH is set + valid, the wrapper loads the
monolithic image instead of libeqapi.so. Explicit lib_path=... to
the Eqlib(...) constructor still wins (early branch in load_library
runs first).

Adds 3 tests from spec §8.1 going through the eqlib path:
test_unset_falls_back_to_per_module, test_set_routes_eqlib,
test_missing_file_raises. Other 5 wrappers wired in next commit;
test_set_routes_all_wrappers (5 of 6) lands after the rollout.

eqlib's own test suite (eqlib/tests/) stays green.

Spec: docs/superpowers/specs/2026-05-23-l7b-ii-phase-2c-pra-loader-contract-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Roll out the priority-0 mono routing hook from eqlib (prior commit)
to trlib, fplib, tilib, wrxlib, and totlib. Pattern is identical
across wrappers — only the per-module <MOD>LIB_PATH name differs.

totlib's hook (D-7): MONO_LIB_PATH overrides TOTLIB_PATH on the
orchestrator wrapper, so a TotPipeline whose Tot has mono routing
gets a consistent broker view across all 6 wrappers it instantiates.

Tests expanded to a parameterized TestAllWrappersRouting that
exercises all 6 wrappers in a single subTest loop for each of:
unset_falls_back_to_per_module, set_routes_all_wrappers,
missing_file_raises.

Per-module test suites (eqlib/tests, trlib/tests, ...) all stay
green — priority-0 insertion is additive when MONO_LIB_PATH is
unset, which is the case for those existing tests.

Spec: docs/superpowers/specs/2026-05-23-l7b-ii-phase-2c-pra-loader-contract-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the remaining 5 spec §8.1 cases on top of the 4 already in
place (1 helper + 3 wrapper-flow):

- test_per_module_env_var_still_honored: pins D-5 "non-mono chain
  unchanged" — uses tempfile-staged fake .so so the assertion is on
  the env-var step, not a side-effect of the fallback file existing.
  (Bugfix: capture mono_so before popping MONO_LIB_PATH from env so
  _mono_path() returns "" rather than ".").
- test_non_mono_so_raises: default libtotapi.so (tot_is_mono==0)
  surfaces "not a mono image" RuntimeError.
- test_wrong_so_without_tot_is_mono_raises: libobjc-trampolines.dylib
  (macOS) / libc.so.6 (Linux) surfaces "no tot_is_mono symbol"
  RuntimeError. Uses _UNRELATED_SO_CANDIDATES with real-on-disk
  requirement (macOS dyld cache paths fail Path.exists() so we skip
  them in favour of /usr/lib/libobjc-trampolines.dylib which is always
  a real file on macOS 12+).
- test_unreadable_so_raises_runtime_error: empty/text file surfaces
  "cannot be dlopen'd" — exercises the §6 try/except OSError wrap.
- test_explicit_constructor_path_still_wins: Tot(lib_path=...) at
  the user-facing constructor level beats MONO_LIB_PATH.

9 cases total; full local run green (9 passed, 0 skipped).

Spec: docs/superpowers/specs/2026-05-23-l7b-ii-phase-2c-pra-loader-contract-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix 1: TestExplicitConstructorPathWins.test_explicit_constructor_path_still_wins
- Move tot.close() into finally block to prevent ctypes handle leak on assertion failure
- Initialize tot=None before try block so finally can safely check before closing

Fix 2: _UNRELATED_SO_CANDIDATES macOS comment
- Add future-proofing note about libobjc-trampolines.dylib fixture
- Flag that test will SKIP (not error) if Apple removes the file
- Reference #208 PR-A Codex 2026-05-25 review MED-2

All 9 tests pass (pytest --forked --timeout=120 --timeout-method=signal).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- tot/tot_api.h: 1 prose paragraph in the existing header docstring.
  No ABI / symbol changes.
- python/totlib/README.md: short "Mono routing" section with usage,
  failure-mode contract, and pointer to PR-B for the behavioral
  follow-up.

Spec: docs/superpowers/specs/2026-05-23-l7b-ii-phase-2c-pra-loader-contract-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends the mono-build job to run python/totlib/tests/
test_mono_routing.py with MONO_LIB_PATH + TOTLIB_PATH both set. The
default libtotapi.so artifact for test_non_mono_so_raises is
already built by the "Build default libtotapi.so" step (PR #207),
so no new build step is required.

This is a sanity test only — no behavioral cross-module rule is
exercised. PR-B will add the TotPipeline.run_pipeline([eq, tr])
integration test that uses this same routing.

Spec: docs/superpowers/specs/2026-05-23-l7b-ii-phase-2c-pra-loader-contract-design.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@k-yoshimi
Copy link
Copy Markdown
Owner Author

@cursor review

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 951c56c. Configure here.

@k-yoshimi k-yoshimi merged commit 699e4f7 into develop May 25, 2026
4 checks passed
k-yoshimi added a commit that referenced this pull request May 26, 2026
Terminal step of Phase 2c. Activates the ("eq","tr") verify rule
in TotPipeline._MONO_ONLY_RULES (now possible after PR-A's
MONO_LIB_PATH plumbing landed at #212) and adds a Layer-D
integration test driving TotPipeline.run_pipeline through eq->tr
on the mono image. PR-B closes #208 and #201.

Spec structure: 12 sections covering context, scope (minimum +
1 negative test), 4 design decisions D-1..D-4 each with chosen/
rejected/why, rule definition (named module-level callable so
error-message repr is meaningful), 2 test cases with try/finally
hygiene and assert-outside-with discipline, CI step in mono-build
job, lifecycle invariants + the empirical resolution of the
tr_init-after-eq.run ordering question, and acceptance criteria.

Codex 2026-05-26 reviewed five rounds:
  - Round 1 (pre-spec proposal): 3 findings (MED-7 timing,
    MED-6 negative-test, LOW-5 assert-outside-with) all addressed.
  - Round 2: SHIP IT with 2 LOW clarifications — D-4 scope
    narrowed; negative test skip-gate from TOTLIB_PATH env to
    per-module .so existence.
  - Round 3: LOW — vestigial TOTLIB_PATH branch in helper, removed.
  - Round 4: 3 LOW — stale TOTLIB_PATH-centric language at §7.3
    docstring, §8 step comment, §8 narrative, and §11 AC#3,
    all cleaned.
  - Round 5: SHIP IT (clean).

Successor: PR-B implementation (writing-plans skill next).
Out of scope: other module-pair rules, Fortran changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@k-yoshimi k-yoshimi deleted the l7b-ii-phase-2c-pra branch May 27, 2026 22:04
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