Skip to content

feat(tot+totlib): mono detection ABI + Layer-C smoke (#201 Phase 2b)#207

Merged
k-yoshimi merged 2 commits into
developfrom
chore/l7b-ii-phase-2b
May 18, 2026
Merged

feat(tot+totlib): mono detection ABI + Layer-C smoke (#201 Phase 2b)#207
k-yoshimi merged 2 commits into
developfrom
chore/l7b-ii-phase-2b

Conversation

@k-yoshimi
Copy link
Copy Markdown
Owner

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

Summary

Phase 2a (PR #206 merged 2026-05-18) made libtotapi_mono.so dlopen-loadable and 1e-10 equivalent. Phase 2b adds the behavioral-activation scaffolding:

  1. tot_is_mono() C ABI — runtime distinguishing between the two builds (0 = default, 1 = mono).
  2. Layer-C smoke test — proves the mono image actually carries shared BPSD broker state by driving eq_run(1)eq_bpsd_put and pulling via tr_check_bpsd_pull on the SAME image (ok=1). Default per-module image returns ok=0.
  3. TotPipeline rule-overlay infrastructure_detect_mono / _active_rules / _MONO_ONLY_RULES.

Critical scope decision: _MONO_ONLY_RULES is EMPTY in Phase 2b

Codex pre-push review caught that populating ("eq","tr") in _MONO_ONLY_RULES here would cause run_pipeline([("eq",...),("tr",...)]) to fire the verify rule against the TotPipeline's Trlib instance, which still loads its OWN per-module libtrapi.so (NOT the mono image). Result: eq pushes to libeqapi.so's PRIVATE bpsd, tr reads from libtrapi.so's PRIVATE bpsd, verify deterministically fails → user-facing regression.

Phase 2b ships the SCAFFOLDING: detection mechanism + infrastructure + Layer-C symbol-chain proof. Phase 2c will populate the rule once the wrappers (Eqlib/Trlib/etc.) are plumbed to route through the mono .so via a shared path.

Files touched

File Change
tot/tot_is_mono.f90 NEW (default returns 0, standalone BIND(C))
tot/tot_is_mono_mono.f90 NEW (mono returns 1, same pattern)
tot/tot_api.f90 doc-only pointer comment
tot/tot_api.h int tot_is_mono(void) declaration
tot/Makefile OBJS_PIC adds tot_is_mono.o; OBJS_PIC_MONO substitutes tot_is_mono_mono.o
python/totlib/_ffi.py tot_is_mono prototype + try/except for legacy builds
python/totlib/pipeline.py _detect_mono (warns on failure), _active_rules, empty _MONO_ONLY_RULES, run_pipeline uses instance lookup
python/totlib/tests/test_mono_bpsd_smoke.py NEW Layer-C (3 tests)
.github/workflows/python-tests.yml mono-build job: build default libtotapi.so + Layer-C step
docs/superpowers/specs/2026-05-18-l7b-ii-phase-2b-design.md NEW spec (committed in first commit, amended in second)

Why standalone-function flag instead of MODULE+PARAMETER

An earlier draft proposed MODULE tot_mono_flag exporting LOGICAL, PARAMETER :: tot_is_mono with tot_api.f90 USEing it. That pattern forces tot_api.o recompile per build target because PARAMETER is inlined at the call site. Standalone-function (the shipped pattern) lets a single tot_api.o participate in both builds — symbol resolved at link time.

Reviewer trail

  • In-house: SHA fcd5e975 → 3 MED + 3 LOW. All addressed in amend 58d44484. Delta re-review: approve.
  • Codex: SHA fcd5e975HOLD (critical: premature rule activation would regress run_pipeline on mono users). Amend empties _MONO_ONLY_RULES. Delta re-review: "amendment is clean".

Test plan

  • Local: default tot_is_mono() = 0; mono = 1
  • Local: 1e-10 equivalence preserved against BOTH builds
  • Local: Layer-C 3/3 pass (test_mono_flag, test_bpsd_round_trip, test_distinguishes_per_module)
  • Local: existing pipeline tests 50/50 pass (1 unrelated Py3.10-incompat preexisting)
  • Local: libtotapi_mono_inspect both HARD GATES still pass
  • CI pytest job: regression on default libtotapi.so path
  • CI mono-build job: build + inspect + 1e-10 equivalence + Layer-C smoke

Out of scope (Phase 2c)

  • Wrapper plumbing — route Eqlib/Trlib/Fplib/Tilib/Wrxlib through the mono .so (likely via MONO_LIB_PATH env var honored by each _ffi.py).
  • Populating _MONO_ONLY_RULES with the ("eq","tr") verify rule (cannot fire usefully until wrapper plumbing exists).

🤖 Generated with Claude Code


Note

Medium Risk
Medium risk because it changes the C ABI surface and build/link inputs for both libtotapi.so and libtotapi_mono.so, and adds new runtime detection paths in TotPipeline that depend on dynamic library loading and warnings behavior.

Overview
Introduces a new C ABI entrypoint tot_is_mono() (0=default per-module, 1=monolithic) by linking a per-target standalone Fortran implementation (tot_is_mono.f90 vs tot_is_mono_mono.f90) and wiring it into tot/Makefile, tot_api.h, and python/totlib/_ffi.py (best-effort prototype for legacy builds).

Adds TotPipeline instance-level coupling-rule overlay: it lazily loads libtotapi to detect mono mode and uses _active_rules to optionally merge _MONO_ONLY_RULES (kept empty in this phase), switching run_pipeline to consult the per-instance rule table.

Adds a new Layer-C pytest test_mono_bpsd_smoke.py that exercises mono broker sharing via eq_run(1) + tr_check_bpsd_pull and verifies the default build reports tot_is_mono()==0, and updates the mono-build GitHub Actions job to also build default libtotapi.so and run this smoke test after the existing mono equivalence test.

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

k-yoshimi and others added 2 commits May 18, 2026 15:05
Brainstorm-output spec for the next #201 PR. Captures the user-confirmed
design choices (2026-05-18):

- Mono detection: new tot_is_mono() C ABI export in tot_api.f90 (Option A
  from spec §3).
- Two-file flag pattern: tot_mono_flag.f90 (default = .FALSE.) +
  tot_mono_flag_mono.f90 (mono = .TRUE.) — Makefile picks the right one
  per build.
- Pipeline conditional rule registration: instance-level _active_rules
  in TotPipeline.__init__, overlaying _MONO_ONLY_RULES (("eq","tr")
  verify rule) when _detect_mono() is True.
- Layer-C scope: direct ctypes calls on the mono .so handle (test_mono_flag,
  test_bpsd_round_trip, test_distinguishes_per_module). Full TotPipeline
  mono routing via Eqlib/Trlib/Fplib/Tilib/Wrxlib is OUT-OF-SCOPE and
  deferred to a future Phase 2c spec.

Codex retrospective findings 2026-05-15 incorporated:
- Spec amendment placement: original L-7b-ii spec §0 already corrected
  in PR #206; Phase 2b leaves it as-is.
- 2-PR split: Phase 2a (build-infra, shipped in PR #206) vs Phase 2b
  (this spec's deliverable, behavioral activation).
- Stale BPSD state isolation: --forked enforced via mono-build CI step.
- Conditional rule registration: instance-level _active_rules pattern
  guarantees rule does NOT activate on default per-module .so.

Acceptance criteria (§9): 10 explicit items including 1e-10 equivalence
preserved, ABI present in both builds, smoke tests pass, pipeline gating
correct, CLAUDE.md compliance verified.

Open questions resolved by user (§11): #1 keep Phase 2b scope tight
(wrapper plumbing deferred to Phase 2c), #3 module name tot_mono_flag.
Open question #2 (eq-push C ABI sequence) flagged for verification
during implementation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2a (PR #206) made libtotapi_mono.so dlopen-loadable and 1e-10
equivalent to libtotapi.so. Phase 2b adds:

1. **Mono detection ABI** (`tot_is_mono` C function) — distinguishes
   the two builds at runtime.
2. **Layer-C smoke test** — proves the mono image actually carries
   shared BPSD broker state by driving eq_run(1) -> eq_bpsd_put on
   the mono .so and pulling via tr_check_bpsd_pull on the SAME image
   (ok=1). The default per-module image returns ok=0 (verified by
   test_distinguishes_per_module).
3. **TotPipeline rule-overlay infrastructure** — `_detect_mono` /
   `_active_rules` / `_MONO_ONLY_RULES`. **The infrastructure is in
   place but `_MONO_ONLY_RULES` is intentionally EMPTY in Phase 2b.**

Why _MONO_ONLY_RULES is empty (Codex pre-push review caught the
trap): populating ("eq","tr") here would cause `run_pipeline` to fire
the verify rule against the TotPipeline's Trlib instance, which
still loads its OWN per-module libtrapi.so (NOT the mono image).
Result: eq pushes to libeqapi.so's PRIVATE bpsd, tr reads from
libtrapi.so's PRIVATE bpsd, verify deterministically fails → user
regression. Phase 2c will plumb the wrappers to route through the
mono .so, at which point ("eq","tr") activation becomes meaningful.

Phase 2b ships the SCAFFOLDING; Phase 2c will populate the rule.

Six changes:

1. tot/tot_is_mono.f90 (NEW, default returns 0) +
   tot/tot_is_mono_mono.f90 (NEW, mono returns 1). Standalone
   BIND(C) functions (NOT inside MODULE) so swapping at link time
   does NOT force tot_api.o recompile. The Makefile picks one per
   build target.

2. tot/tot_api.h declares `int tot_is_mono(void)` with usage docs.
   tot/tot_api.f90 carries a doc-only pointer to the standalone files.

3. tot/Makefile OBJS_PIC adds tot_is_mono.o (default build),
   OBJS_PIC_MONO substitutes tot_is_mono_mono.o (mono build).

4. python/totlib/_ffi.py attaches tot_is_mono prototype with
   try/except AttributeError for legacy build compatibility.

5. python/totlib/pipeline.py adds `_MONO_ONLY_RULES` (empty),
   `_detect_mono()` (with warnings.warn on failure paths for
   debuggability — Codex retrospective 2026-05-18 LOW finding),
   `_build_active_rules()`, `_get_active_rules()`. `run_pipeline`
   uses `_get_active_rules()` instead of `COUPLING_RULES` directly.

6. python/totlib/tests/test_mono_bpsd_smoke.py (NEW, Layer-C):
   - C-1 (test_mono_flag): mono reports tot_is_mono() == 1.
   - C-2 (test_bpsd_round_trip): drives eq_init + eq_set_param +
     eq_run(1) on the mono image; eq_run(1) hits eq_load -> eq_bpsd_put;
     then tr_check_bpsd_pull on the SAME image returns ok=1. Uses
     PR #199's committed eqdata-HT6M fixture in a chdir'd tmpdir.
   - C-3 (test_distinguishes_per_module): default libtotapi.so
     reports tot_is_mono() == 0.

CI mono-build job extended with:
- "Build default libtotapi.so" step (needed for C-3)
- "Layer-C BPSD broker smoke (mono image vs default)" step
  (--forked + MONO_LIB_PATH + TOTLIB_PATH)

Local verification (macOS arm64, gfortran 15.x):
  - Default tot_is_mono() = 0 ✓
  - Mono    tot_is_mono() = 1 ✓
  - 1e-10 equivalence preserved against BOTH builds ✓
  - Layer-C 3/3 pass ✓
  - Existing pipeline tests (test_pipeline*.py): 50/50 pass ✓
  - libtotapi_mono_inspect: both HARD GATES (8-bpsd + no-.so-deps) ✓
  - TotPipeline default: active_rules = {('fp','tr')}
  - TotPipeline mono:    active_rules = {('fp','tr')} (empty
    _MONO_ONLY_RULES per Codex pre-push review)

Phase 2c follow-up scope (separate issue/PR):
- Route Eqlib/Trlib/Fplib/Tilib/Wrxlib through a single mono .so
  (e.g. MONO_LIB_PATH env var honored by each _ffi.py).
- Populate _MONO_ONLY_RULES with the ("eq","tr") verify rule.

Spec: docs/superpowers/specs/2026-05-18-l7b-ii-phase-2b-design.md
  (committed earlier on this branch + amended in this commit to
  reflect the shipped implementation and the empty-rule-dict
  decision)

Reviewer drivers: Codex retrospective 2026-05-15 items 1, 3, 5, 6
+ Codex pre-push review 2026-05-18 (caught the wrapper-plumbing
trap and forced the empty _MONO_ONLY_RULES decision).

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 58d4448. Configure here.

@k-yoshimi k-yoshimi merged commit c30b86e into develop May 18, 2026
4 checks passed
k-yoshimi added a commit that referenced this pull request May 25, 2026
Design for the next step in the Phase 2 chain. PR-A introduces a
single authoritative mono-routing rule (MONO_LIB_PATH env var +
shared python/_runtime_mode.py helper) so every wrapper
(eqlib/trlib/fplib/tilib/wrxlib/totlib) inside or outside
TotPipeline loads the same libtotapi_mono.so when activated.
Codex's "single authoritative loader semantics" constraint
(2026-05-18 retrospective) is the load-bearing requirement.

Spec structure: 12 sections covering context, scope (PR-A only —
NO rule activation, that's PR-B), 7 design decisions D-1..D-7 each
with chosen path + rejected alternatives + rationale, helper API
contract (lru_cache + strict verify + OSError wrap), per-wrapper
patch shape, 9 test cases with explicit coverage matrix, CI
integration leaning on the existing PR #207 default-build step,
risks with mitigations, and acceptance criteria.

Reviewed three rounds by Codex (codex:codex-rescue):
  - Pass 1: 3 MED + 3 LOW — all addressed
  - Pass 2: 2 LOW (test fixture mismatch + acceptance count stale)
            — both addressed (test split into hasattr-guard path
              and OSError-wrap path; §11 count updated to 9)
  - Pass 3: HIGH/MED/LOW = 0 — SHIP IT

Plus an architecture sub-review for the helper module placement —
Codex independently confirmed Option A (python/_runtime_mode.py)
over totlib-owned (layering inversion) and subpackage (YAGNI).

Successor: PR-A implementation (this branch on dev next session).
Out of scope: PR-B rule activation + TotPipeline integration test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@k-yoshimi k-yoshimi deleted the chore/l7b-ii-phase-2b 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