Files
logos-basecamp/.github
Dario LipicarandClaude Opus 5 2c32cfef22 fix(ci): run the symbol gate — on Windows too, where it matters (#354)
* fix(ci): run the symbol gate, and teach it the Windows layout

The gate was exposed in flake.nix `checks` and built by nothing. Both
workflows enumerate individual `nix build .#<output>` steps by hand and no
`nix flake check` runs in this repo, so `git grep -E 'symbol-gate|flake check'
-- .github/` returned nothing: the one control that mechanically enforces the
one-runtime invariant was dead code. A PR relinking the logos runtime into
main_ui would have merged green — and on Linux and macOS it would have RUN
green too, because both interpose a duplicate definition away at load time.

Two things were wrong with the gate itself, both specific to the platform
where a duplicate is actually fatal:

  * the peer-image sweep globbed `$ROOT/lib` for *.dylib/*.so only. On Windows
    every liblogos_* shared image is staged into bin/ as a .dll, so the sweep
    matched nothing, no definer was found, and the exactly-one assertion
    reported "0 definers" for TokenManager, LogosAPI and LogosAPIClient — it
    could not pass on a correct tree. Its three sibling probes (provider,
    negative control, consumer) had each been taught bin/*.dll; this one had
    not.

  * `nm -D` was selected for every non-Darwin target including the mingw
    cross, and a PE has no ELF dynamic symbol table. Measured against a real
    mingw PE (libffi-8.dll, binutils 2.46): `nm -D` reads 0 lines and errors,
    plain `nm` reads 687, `nm --defined-only` 686. valid() would have caught
    that and aborted the gate as vacuous rather than passing it, so this was
    fail-closed — but the gate would never once have run on Windows.

x86_64-windows is not wired into CI here: that attribute cannot be evaluated
as pinned (see flake.nix's binBundleDir note), a pre-existing blocker. The
gate now understands the layout, so wiring it is a one-line follow-up.

Also drops two stale comments that pointed at LogosSharedFromDll.cmake, the
single-provider shim deleted in #348, and the claim that liblogos_core is the
provider — liblogos_protocol and liblogos_qt_host define these types now, which
is exactly why the assertion is exactly-one rather than naming an owner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(symbol-gate): call nm and c++filt by their target-prefixed names

The cross bintools installs ONLY x86_64-w64-mingw32-nm and
x86_64-w64-mingw32-c++filt; there is no bare `nm` or `c++filt` on PATH inside
the Windows derivation. Every measurement therefore produced nothing, and
valid() refused to assert over an empty read -- so the gate got as far as
identifying the right images and then stopped:

  provider  = bin/liblogos_core.dll
  consumers = bin/LogosBasecamp.exe
              plugins/package_manager_ui/package_manager_ui_replica_factory.dll
              plugins/main_ui/main_ui.dll
  == each runtime type is defined by EXACTLY ONE image ==
    liblogos_core.dll   ERROR: nm read 0 symbols — vacuous

That is the vacuity guard working exactly as intended: fail-closed rather than
report a reassuring zero. But it meant the gate could never actually run on
Windows. The tool was missing, not incapable -- the host's own nm reads these
PEs fine (7350 symbols out of liblogos_core.dll).

stdenv.cc.targetPrefix is "" natively, so this is a no-op on Linux and macOS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(symbol-gate): do not count PE import thunks as definitions

With nm reachable, the gate ran on Windows for the first time and reported a
split-brain that is not there:

  TokenManager    4 definers: liblogos_core.dll(3) LogosBasecamp.exe(1)
                              liblogos_protocol.dll(41) liblogos_qt_host.dll(5)

liblogos_core.dll defines ZERO runtime symbols by design, and measures 0 on
macOS. The Windows numbers are an artifact of PE: for every imported function
GNU ld synthesizes a jump stub in .text AND an __imp_<mangled> slot in the
import address table, and `nm --defined-only` reports the stub as `T`. The
pairing is visible directly:

  I __imp__ZN12TokenManager8instanceEv
  T TokenManager::instance()

So the filter is that pairing: a symbol counts as DEFINED only when the image
has no __imp_ slot for it. That is the right discriminator rather than merely a
working one -- a genuine second copy statically linked into an image has no
__imp_ slot and still counts, which is exactly the case this gate exists to
catch.

The PE export table would also have suppressed the phantom (liblogos_core.dll
exports 0 TokenManager symbols, liblogos_protocol.dll exports 45), and it was
the first thing I reached for. It is the wrong tool here: a real private copy
is absent from the export table too, so it would trade this false positive for
a false negative.

Measured on the cross build, the filter reproduces the macOS shape exactly:

  liblogos_core.dll          TokenManager 0    LogosAPI/Client 0
  liblogos_protocol.dll                   41                   109
  liblogos_qt_host.dll                     0                    29
  LogosBasecamp.exe                        0                     0
  main_ui.dll                              0                     0
  package_manager_ui_replica_factory.dll   0                     0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* ci(windows): run the symbol gate where it actually matters

The gate now evaluates, builds and passes for x86_64-windows, so wire it into
build-windows and drop the caveat the other two jobs carried saying it could
not be. PE has no symbol interposition, so a duplicate runtime that Linux and
macOS silently collapse to one is fatal only here -- and CI cross-builds this
bundle and never runs it, which makes a build-time assertion the only signal
that exists.

Measured on a real cross build (needs an x86_64-linux builder; logos_build_info.h
is an x86_64-linux derivation, so this cannot run from an aarch64-darwin host):

  TokenManager     1 definer: liblogos_protocol.dll(41)   OK
  LogosAPI         1 definer: liblogos_qt_host.dll(32)    OK
  LogosAPIClient   1 definer: liblogos_protocol.dll(122)  OK
  TIER 1  LogosBasecamp.exe 0 · main_ui.dll 0 · pmui_replica_factory.dll 0
  SYMBOL GATE: PASS

and the negative control rejects a planted duplicate on PE too, catching 41
TokenManager and 122 LogosAPIClient definitions including the one that names
the failure exactly:

  guard variable for TokenManager::instance()::instance

That control is what makes the pass mean something. It also proves the __imp_
thunk filter does not over-reach: a genuine private copy is still detected at
full strength.

Also corrects the binBundleDir note, which was stale in BOTH of its claims --
logos-package-manager-ui not cross-compiling, and the separate EVAL-time
blocker in logos-package-downloader-module. Both were fixed upstream. The
bundle the gate just ran against contains package_manager_ui, so the tree the
note's measurements describe is missing a plugin that is no longer missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 19:15:45 -03:00
..