Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 44 additions & 6 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -474,20 +474,58 @@ jobs:
run: |
make -C tot libtotapi_mono.so

- name: Inspect libtotapi_mono.so (8-bpsd invariant hard-gated)
# libtotapi_mono_inspect runs one hard-gate plus several
- name: Inspect libtotapi_mono.so (8-bpsd + no-lib*api.so-deps hard-gated)
# libtotapi_mono_inspect runs TWO hard-gates plus several
# observational checks:
#
# HARD GATE: BPSD storage symbol count == 8 (one per bpsd module
# HARD GATE 1: BPSD storage symbol count == 8 (one per bpsd module
# type: device + equ1d + metric1d + plasmaf + shot + species +
# trmatrix + trsource). If the per-module dedup ever fails
# and we get 5×8=40 (or any other count), the build fails.
#
# HARD GATE 2: No `lib*api.so` runtime dep in the .so's NEEDED
# list (Linux `ldd`) / load commands (macOS `otool -L`). The
# whole point of the mono build is that it has no per-module
# .so deps; if any leak in, fail loudly.
#
# Observational (printed, NOT enforced as exit code):
# - ldd / otool -L output (should have no lib*api.so deps;
# visual inspection only until we standardize the regex
# across Linux/macOS).
# - exported tot_* symbol list.
# - file size.
run: |
make -C tot libtotapi_mono_inspect

- name: Set up Python (for the mono equivalence step below)
# The mono-build job is otherwise Python-free; pytest is only
# needed for the equivalence step. Pinning to 3.11 (one of the
# versions the pytest job's matrix exercises) keeps the
# behavior aligned without doubling on both 3.11 + 3.13.
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install pytest deps for the mono equivalence step
run: |
python -m pip install --upgrade pip
pip install pytest pytest-forked pytest-timeout pytest-mock

- name: 1e-10 equivalence test against libtotapi_mono.so
# Phase 2a deliverable (#201): with the unified
# tot_graphics_stubs_mono.f90 in place, libtotapi_mono.so is
# now dlopen-loadable via Python ctypes. Running the existing
# totlib equivalence fixtures against it verifies the mono
# image is a faithful drop-in replacement for the default
# libtotapi.so on the existing test surface — required for
# CLAUDE.md "equivalence tests at 1e-10 MUST pass."
#
# These fixtures don't exercise BPSD-mediated coupling, so
# this is a regression check that the mono build hasn't
# broken the existing path. The Layer-C BPSD smoke test that
# actually validates monolithic broker sharing is deferred
# to #201 Phase 2b.
env:
PYTHONDONTWRITEBYTECODE: "1"
PYTHONPATH: python
TOTLIB_PATH: ${{ github.workspace }}/tot/libtotapi_mono.so
run: |
python -m pytest python/totlib/tests/test_equivalence.py -v \
--forked --timeout=120 --timeout-method=signal
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,68 @@ libeqapi.so 側の `bpsd_put_equ1D` は libtrapi.so 側の `bpsd_get_equ1D`
`python/trlib/README.md` の "BPSD ブローカー pull 検証" 節 (両方とも
日本語) を参照。

### Correction note (2026-05-14)

`nm` / `otool` による実シップ artifact の検証で、上記
2026-05-04 のメモが置いた前提「レガシー `Tot` / `libtotapi.so`
は eq + tr + bpsd を同一 .so に co-link している」は、現行ビルドに
ついては **誤り** であることが判明した。実際の `libtotapi.so` は
per-module の `libeqapi.so` / `libtrapi.so` / `libfpapi.so` /
`libtiapi.so` / `libwrxapi.so` を別個の runtime shared library として
依存しており (`otool -L tot/libtotapi.so` で確認)、bpsd storage
シンボルを自身では一切持たない (`nm tot/libtotapi.so | grep
'___bpsd_.*_MOD_.*x_init_flag'` は 0 行)。

既存の tot 等価性テスト (tot_demo2014_short / tot_ht6m_short) が
1e-10 で通る理由は、fixture が **BPSD 仲介の profile coupling
パスを exercise していない** からである。等価性テストは
orchestrator fan-out (init/run/get_state/finalize) は検証するが、
cross-module broker round-trip は通っていない。つまり旧 §0
の「co-link しているため影響を受けない」という記述は、現状では
「coupling path が test されていないだけで、構造的には同じ
isolation 問題を抱えている」が正しい。

採用方針: 上記候補 (a)〜(d) のうち **(a) 共有 .so への再構成** を
採用する。ただし元の表現「共有 .so」は曖昧で、実装は厳密には
「**monolithic libtotapi_mono.so** — per-module PIC オブジェクト
全部 + 単一の bpsd PIC セットを 1 つの image に co-link する
ビルドターゲット」を指す。Codex 独立 review (2026-05-15) で
4 候補は「正しい問い設定ではない — broker semantics は 1 つの
broker-owning runtime image を要求する」と指摘され、5 番目の
オプション (true monolithic image) が技術的にも保守性的にも
最良と判定された。`TotPipeline` は scalar coupling 専用に scope
を狭め、broker-mediated coupling は monolithic image の下でのみ
動作させる。

PoC ブランチ `chore/l7b-ii-monolithic-poc` (PR #202、merge
`7d18264e`) が `tot/Makefile` 変更のみで `libtotapi_mono.so`
が **link** することを実証した。BPSD storage シンボルは 1 セット
にデデュープ済 (`nm` で 8 unique シンボル、5×8=40 ではない)。
ただしこの artifact は **まだ dlopen-loadable ではない**:
~30 個のグラフィクス stub シンボル (`_getkgt_`、`_getkrt_`、
`_contX_`、`_r2w2b_`、`_text_` 等) が個別 `<mod>_graphics_stubs.o`
ファイル固有で、mono ビルドが 1 つの stub ファイルだけ保持する
構成 (最広域の fp の stubs) では未定義のまま残る。Phase 2a で
これら全てを 1 つの新規 `tot/tot_graphics_stubs_mono.f90` に統合
することで dlopen-loadable にする (= 本 issue (#201) Phase 2a の
core deliverable)。

Phase 2a の Linux 互換性は PR #205 (`mono-build` CI job) で既に
カバー済み — Linux ld ブランチ (`--start-group/--end-group` +
`--unresolved-symbols=ignore-all` + `-soname`) も macOS と同等に
動作する。

詳細な調査ログ:
`docs/superpowers/specs/2026-05-14-l7b-ii-monolithic-poc-findings.md`

`("eq","tr")` ルールの実 register と Layer C 統合テストは Phase 2b
(別 PR、#201 内) で行う。Phase 2b では、stale BPSD state 問題
(本節前段で言及) に対応するため process isolation (`--forked`)
または明示的な broker reset を Layer C テストの前提条件とする。
また、ルールの register は **mono runtime 選択時の条件付き**にし、
default `libtotapi.so` path で誤って activate されないようにする
(Codex retrospective 2026-05-15 item 5)。

---

## §1. Overview and data flow
Expand Down
54 changes: 38 additions & 16 deletions tot/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,14 @@ FP_PIC_OBJS_MONO_RAW = $(wildcard ../fp/obj/pic/*.o ../fp/obj/pic/*/*.o)
TI_PIC_OBJS_MONO_RAW = $(wildcard ../ti/obj/pic/*.o ../ti/obj/pic/*/*.o)
WRX_PIC_OBJS_MONO_RAW = $(wildcard ../wrx/obj/pic/*.o ../wrx/obj/pic/*/*.o)

# Phase 2a (2026-05-18): all 5 per-module <mod>_graphics_stubs.o files
# are now EXCLUDED. The unified tot_graphics_stubs_mono.f90 source
# (built into tot/obj/pic/tot_graphics_stubs_mono.o) supplies the
# full union of graphics-stub symbols across eq/tr/fp/ti/wrx, so
# none of the per-module stubs participate in the mono link.
EQ_PIC_OBJS_MONO = $(filter-out ../eq/obj/pic/bpsd/% ../eq/obj/pic/eq_graphics_stubs.o,$(EQ_PIC_OBJS_MONO_RAW))
TR_PIC_OBJS_MONO = $(filter-out ../tr/obj/pic/bpsd/% ../tr/obj/pic/tr_graphics_stubs.o,$(TR_PIC_OBJS_MONO_RAW))
FP_PIC_OBJS_MONO = $(filter-out ../fp/obj/pic/bpsd/%,$(FP_PIC_OBJS_MONO_RAW))
FP_PIC_OBJS_MONO = $(filter-out ../fp/obj/pic/bpsd/% ../fp/obj/pic/fp_graphics_stubs.o,$(FP_PIC_OBJS_MONO_RAW))
TI_PIC_OBJS_MONO = $(filter-out ../ti/obj/pic/bpsd/% ../ti/obj/pic/ti_graphics_stubs.o,$(TI_PIC_OBJS_MONO_RAW))
WRX_PIC_OBJS_MONO = $(filter-out ../wrx/obj/pic/bpsd/% ../wrx/obj/pic/wrx_graphics_stubs.o,$(WRX_PIC_OBJS_MONO_RAW))

Expand Down Expand Up @@ -619,12 +624,18 @@ else
SO_SONAME_MONO = -Wl,-soname,libtotapi_mono.so
endif

# tot's own PIC objects MINUS tot_graphics_stubs.o. The mono build
# uses fp_graphics_stubs.o instead, which is a strict superset of
# tot's stub set for the symbols both provide (guclip_, gudate_,
# guflsh_, gutime_, pagee_, pages_) plus grd1d_mod/grd2d_mod/draw2d_/
# eqgout_/move2d_ that some module code paths reference.
OBJS_PIC_MONO := $(filter-out $(OBJDIR_PIC)/tot_graphics_stubs.o,$(OBJS_PIC))
# tot's own PIC objects MINUS tot_graphics_stubs.o, PLUS the new
# unified mono stubs object. tot_graphics_stubs_mono.f90 is a hand-
# authored union of all 5 per-module <mod>_graphics_stubs.f90 files
# (eq + tr + fp + ti + wrx), with each symbol defined exactly once
# and ABI choices documented in the source header (notably GUTIME
# picks the REAL signature; EQGOUT picks the 1-arg signature). It
# is used ONLY by the mono build path; the default libtotapi.so
# continues to use tot_graphics_stubs.o through OBJS_PIC.
OBJS_PIC_MONO := $(filter-out $(OBJDIR_PIC)/tot_graphics_stubs.o,$(OBJS_PIC)) \
$(OBJDIR_PIC)/tot_graphics_stubs_mono.o

$(OBJDIR_PIC)/tot_graphics_stubs_mono.o : tot_graphics_stubs_mono.f90

# libtotapi_mono.so depends on the per-module .so files (LIBS_PIC_SO)
# as real-file prerequisites — the same prereq pattern as
Expand All @@ -637,7 +648,7 @@ OBJS_PIC_MONO := $(filter-out $(OBJDIR_PIC)/tot_graphics_stubs.o,$(OBJS_PIC))
# libtotapi_mono.so only re-links if a per-module .so is newer.
# Using a .PHONY prereq target here would force unconditional re-link
# on every invocation (Bugbot LOW finding on PR #202, 2026-05-14).
libtotapi_mono.so: $(OBJS_PIC) $(LIBS_PIC_SO)
libtotapi_mono.so: $(OBJS_PIC_MONO) $(LIBS_PIC_SO)
@if [ -z "$(strip $(EQ_PIC_OBJS_MONO))" ] || [ -z "$(strip $(TR_PIC_OBJS_MONO))" ] || \
[ -z "$(strip $(FP_PIC_OBJS_MONO))" ] || [ -z "$(strip $(TI_PIC_OBJS_MONO))" ] || \
[ -z "$(strip $(WRX_PIC_OBJS_MONO))" ] || [ -z "$(strip $(OBJBPSD_PIC_MONO))" ]; then \
Expand All @@ -657,20 +668,31 @@ libtotapi_mono.so: $(OBJS_PIC) $(LIBS_PIC_SO)
libtotapi_mono_inspect: libtotapi_mono.so
@echo "=== file ==="
@file libtotapi_mono.so
@echo "=== link deps (should NOT contain any lib*api.so dependency) ==="
@( ldd libtotapi_mono.so 2>/dev/null || otool -L libtotapi_mono.so ) | grep -vE '^libtotapi_mono\.so:' | grep -iE 'libtotapi_mono|api\.so|bpsd|lib' | head -20
@echo "=== bpsd MOD-private storage symbols (must be 1 set, not 5) ==="
@nm libtotapi_mono.so | grep -E '__bpsd_.*_MOD_.*x_init_flag' | sort -u
@echo "=== bpsd storage symbol count check (must be 8: device + equ1d + metric1d + plasmaf + shot + species + trmatrix + trsource) ==="
@echo "=== link deps ==="
@( ldd libtotapi_mono.so 2>/dev/null || otool -L libtotapi_mono.so )
@echo "=== HARD GATE 1: bpsd storage symbol count must be exactly 8 (device + equ1d + metric1d + plasmaf + shot + species + trmatrix + trsource) ==="
@count=$$(nm libtotapi_mono.so | grep -E '__bpsd_.*_MOD_.*x_init_flag' | sort -u | wc -l | tr -d ' '); \
if [ "$$count" != "8" ]; then \
echo " FAIL: expected 8 unique bpsd storage symbols, found $$count"; exit 1; \
echo " FAIL: expected 8 unique bpsd storage symbols, found $$count"; \
nm libtotapi_mono.so | grep -E '__bpsd_.*_MOD_.*x_init_flag' | sort -u; \
exit 1; \
else \
echo " OK: 8 unique bpsd storage symbols (one per bpsd module type)"; \
fi
@echo "=== exported tot_* symbols ==="
@echo "=== HARD GATE 2: no per-module lib*api.so runtime deps (the mono build must be self-contained) ==="
@deps=$$( ( ldd libtotapi_mono.so 2>/dev/null || otool -L libtotapi_mono.so ) \
| grep -E 'lib(eq|tr|fp|ti|wr|wrx|tot)api\.so' \
| grep -v 'libtotapi_mono\.so' || true ); \
if [ -n "$$deps" ]; then \
echo " FAIL: unexpected per-module lib*api.so deps:"; \
echo "$$deps"; \
exit 1; \
else \
echo " OK: no per-module lib*api.so runtime deps"; \
fi
@echo "=== exported tot_* symbols (informational) ==="
@nm libtotapi_mono.so | grep -E ' T _?tot_' | head -10
@echo "=== size ==="
@echo "=== size (informational) ==="
@ls -la libtotapi_mono.so

# ---------------------------------------------------------------------
Expand Down
Loading
Loading