The use-after-free fixed in #47 (09f684f) had no test in this repo, and could
not have had one by accident: it needs TWO subscriptions to a module that is not
reachable yet, plus an event-loop turn afterwards, and it kills the process in
QtRO's connect list rather than failing an assertion at the call site. It was
caught downstream, by logos-view-module-runtime's suite -- a repo that had no CI
at all until two days ago.
WHAT IT REPRODUCES. tryAcquireNow() used to delete a dynamic replica that was
not yet Valid. QtRO shares one replica IMPLEMENTATION per object name per node,
and while that implementation is still waiting for the source's metaobject it
records every facade built on it as a RAW pointer in m_parentsNeedingConnect.
~QRemoteObjectReplica is empty and never deregisters, so the implementation
dereferences freed memory when the class definition arrives.
WHY TWO. The first probe owns the only implementation and takes it down with
itself. It takes a second, sharing an implementation pinned by an in-flight
PendingAcquire, before a freed facade can outlive the implementation pointing at
it. Subscribe once and nothing is wrong; register your events up front, the way
a QML view does, and it dies. The single-subscription control is here to pin
that asymmetry, so a reader cannot conclude the whole path was broken.
THE FAILURE MODE IS A DEAD BINARY, not a red assertion, and it happens on the
pump at the end rather than in the subscribe loop -- without that event-loop
turn the pre-fix code passes. Surviving to the assertions IS the regression
check; delivery is asserted as well so a build that armed nothing cannot pass by
being inert.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
logos-protocol
The Logos protocol layer: transports, token exchange, and the
language-neutral lp_* C ABI (cpp/logos_protocol.h) that every Logos
SDK builds on.
Extracted from logos-cpp-sdk so that non-C++ SDKs (Rust, …) consume the
same transports, capability/token flow and wire behavior through one stable,
versioned boundary instead of re-wrapping the C++/Qt SDK.
What lives here
- Public C ABI —
cpp/logos_protocol.h: consumer surface (lp_client_*,lp_invoke[_async],lp_subscribe, tokens,lp_get_methods), provider groundwork (lp_provider_*), and the protocol version (LOGOS_PROTOCOL_VERSION_*,lp_protocol_version(),lp_protocol_abi_major()). JSON-in-strings data model; bytes cross the boundary as{"_bytes":"<base64url>"}(lossless, NUL-safe). - Transports — plain TCP / TCP+TLS (Boost.Asio + OpenSSL + nlohmann,
Qt-free),
qt_local, in-memory mock, and Qt Remote Objects (qt_remote— the only Qt-bearing transport). - Consumer core —
LogosAPIClient/LogosAPIConsumerincluding the automaticcapability_module.requestModuletoken-fetch flow (behind the protocol boundary: every language gets it for free). - Provider-side plumbing —
ModuleProxy(auth gate the transports publish) and the abstractLogosProviderObjectinterface (logos_provider_interface.h). - Token manager, transport/registry factories, mode config (remote/local/mock), and the canonical QVariant↔JSON conversion used at the QRO boundary.
logos-cpp-sdk layers the typed C++ developer API (LogosAPI, module
context, code generator, provider base classes) on top of this repo.
Versioning
This repo carries the logos-protocol semver — the single number that governs Logos load/call compatibility. Two participants (modules, hosts, SDKs in any language) interoperate iff they share the same MAJOR. MINOR is additive/back-compatible; PATCH never affects compatibility. SDKs must re-expose the version of the protocol they linked (never mint their own).
Building
# Via workspace
ws build logos-protocol
# Standalone
nix build
# Tests
nix build .#tests
Layering invariant
logos-protocol depends only on Qt / Boost / OpenSSL / nlohmann_json — it
must NEVER depend on logos-cpp-sdk, logos-qt-sdk, logos-rust-sdk, liblogos
or logos-lidl. Everything points inward.