2026-08-19 15:50:49 -03:00
|
|
|
#ifndef LOGOS_API_H
|
|
|
|
|
#define LOGOS_API_H
|
|
|
|
|
|
|
|
|
|
#include "logos_mode.h"
|
|
|
|
|
#include "logos_shared_api.h"
|
|
|
|
|
#include "logos_transport_config.h"
|
|
|
|
|
#include "logos_types.h"
|
|
|
|
|
|
|
|
|
|
#include <QHash>
|
|
|
|
|
#include <QHashFunctions>
|
|
|
|
|
#include <QObject>
|
|
|
|
|
#include <QString>
|
|
|
|
|
|
|
|
|
|
#include <functional>
|
|
|
|
|
#include <optional>
|
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
|
|
class LogosAPIClient;
|
|
|
|
|
class LogosAPIProvider;
|
|
|
|
|
class TokenManager;
|
|
|
|
|
|
|
|
|
|
// qHash for LogosTransportConfig — combined with operator== from
|
|
|
|
|
// logos_transport_config.h, this lets QHash use it as a key. Lives here
|
|
|
|
|
// rather than in logos_transport_config.h so that header stays Qt-free
|
|
|
|
|
// (the SDK is being de-Qt'd; only Qt-using consumers like the cache
|
|
|
|
|
// here pull in the QHash adapter).
|
|
|
|
|
//
|
|
|
|
|
// Every field that distinguishes one explicit-transport client from
|
|
|
|
|
// another contributes to the hash; otherwise two callers with different
|
|
|
|
|
// TLS or codec settings could land on the same bucket and cache-alias
|
|
|
|
|
// onto a single client.
|
|
|
|
|
inline size_t qHash(const LogosTransportConfig& cfg, size_t seed = 0) noexcept
|
|
|
|
|
{
|
|
|
|
|
return qHashMulti(seed,
|
|
|
|
|
static_cast<int>(cfg.protocol),
|
|
|
|
|
std::hash<std::string>{}(cfg.host),
|
|
|
|
|
cfg.port,
|
|
|
|
|
std::hash<std::string>{}(cfg.caFile),
|
|
|
|
|
std::hash<std::string>{}(cfg.certFile),
|
|
|
|
|
std::hash<std::string>{}(cfg.keyFile),
|
|
|
|
|
cfg.verifyPeer,
|
|
|
|
|
static_cast<int>(cfg.codec));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LogosAPIClient cache key. Mirrors the factory's transport-resolution
|
|
|
|
|
// rule so two callers that would observe the same connection share a
|
|
|
|
|
// cached client:
|
|
|
|
|
//
|
|
|
|
|
// - Mock / Local mode → transport is ignored at construction; key
|
|
|
|
|
// ignores it too. Switching mode changes the
|
|
|
|
|
// key (so cached clients don't bleed across
|
|
|
|
|
// mode switches in tests).
|
|
|
|
|
// - Remote mode → cfg picks the wire endpoint; key includes
|
|
|
|
|
// the full LogosTransportConfig.
|
|
|
|
|
//
|
|
|
|
|
// Without the mode-aware comparison, calling
|
|
|
|
|
// `getClient(x, tcp)` and `getClient(x, tcp_ssl)` in Mock mode would
|
|
|
|
|
// allocate two clients pointing at functionally identical
|
|
|
|
|
// MockTransportConnections.
|
|
|
|
|
struct LogosAPIClientCacheKey {
|
|
|
|
|
QString target;
|
|
|
|
|
LogosMode mode;
|
|
|
|
|
LogosTransportConfig transport; // only compared when mode == Remote
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
inline bool operator==(const LogosAPIClientCacheKey& a,
|
|
|
|
|
const LogosAPIClientCacheKey& b) noexcept
|
|
|
|
|
{
|
|
|
|
|
if (a.target != b.target) return false;
|
|
|
|
|
if (a.mode != b.mode) return false;
|
|
|
|
|
return a.mode == LogosMode::Remote ? a.transport == b.transport : true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline size_t qHash(const LogosAPIClientCacheKey& k, size_t seed = 0) noexcept
|
|
|
|
|
{
|
|
|
|
|
if (k.mode == LogosMode::Remote) {
|
|
|
|
|
return qHashMulti(seed, k.target, static_cast<int>(k.mode), k.transport);
|
|
|
|
|
}
|
|
|
|
|
// Mock / Local: transport is irrelevant — leave it out of the hash
|
|
|
|
|
// so it can't bias which bucket the key lands in.
|
|
|
|
|
return qHashMulti(seed, k.target, static_cast<int>(k.mode));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief LogosAPI provides a unified interface to the Logos SDK
|
|
|
|
|
*
|
|
|
|
|
* This class initializes and keeps instances of the client provider and token manager.
|
|
|
|
|
*
|
2026-08-20 22:29:02 -03:00
|
|
|
* Marked because this is the object handed across the DLL boundary: the host
|
|
|
|
|
* constructs a LogosAPI and passes the pointer to the UI plugin through
|
|
|
|
|
* PluginInterface::logosAPI. Its constructor caches `&TokenManager::instance()`,
|
|
|
|
|
* so an image that links its own copy of logos_api.cpp caches a DIFFERENT
|
|
|
|
|
* singleton than the one the host wrote the token into. Importing instead of
|
|
|
|
|
* re-linking is what makes the two agree.
|
|
|
|
|
*
|
|
|
|
|
* LOGOS_QT_HOST_API, not LOGOS_SHARED_API, because LogosAPI is owned by THIS
|
|
|
|
|
* library while TokenManager and LogosAPIClient are owned by logos-protocol.
|
|
|
|
|
* While building logos_qt_host_shared the first must be EXPORTED and the second
|
|
|
|
|
* two IMPORTED, and one macro cannot say both in the same translation unit. Off
|
|
|
|
|
* Windows the distinction is moot -- both resolve to default visibility -- which
|
|
|
|
|
* is exactly why getting it wrong would go unnoticed until a Windows build.
|
2026-08-19 15:50:49 -03:00
|
|
|
* See logos_shared_api.h in logos-protocol.
|
|
|
|
|
*/
|
2026-08-20 22:29:02 -03:00
|
|
|
class LOGOS_QT_HOST_API LogosAPI : public QObject
|
2026-08-19 15:50:49 -03:00
|
|
|
{
|
|
|
|
|
Q_OBJECT
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
/**
|
|
|
|
|
* @brief Construct a new LogosAPI instance
|
|
|
|
|
* @param module_name The name of this module
|
|
|
|
|
* @param parent Parent QObject
|
|
|
|
|
*/
|
|
|
|
|
explicit LogosAPI(const QString& module_name, QObject *parent = nullptr);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Construct a new LogosAPI with an explicit transport set.
|
|
|
|
|
*
|
|
|
|
|
* `transports` is empty ⇒ use the process-global default (back-compat).
|
|
|
|
|
* Non-empty ⇒ provider publishes on every configured transport
|
|
|
|
|
* (e.g. a daemon listing both LocalSocket and TCP+SSL so the CLI has
|
|
|
|
|
* a fast in-process path *and* remote clients have a secure path).
|
|
|
|
|
*/
|
|
|
|
|
LogosAPI(const QString& module_name,
|
|
|
|
|
LogosTransportSet transports,
|
|
|
|
|
QObject *parent = nullptr);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Construct a new LogosAPI instance (const char* overload — resolves ambiguity)
|
|
|
|
|
*/
|
|
|
|
|
explicit LogosAPI(const char* module_name, QObject *parent = nullptr)
|
|
|
|
|
: LogosAPI(QString(module_name), parent) {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Construct with const char* and explicit transport set.
|
|
|
|
|
*/
|
|
|
|
|
LogosAPI(const char* module_name, LogosTransportSet transports, QObject *parent = nullptr)
|
|
|
|
|
: LogosAPI(QString(module_name), std::move(transports), parent) {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Construct a new LogosAPI instance (std::string overload)
|
|
|
|
|
*/
|
|
|
|
|
explicit LogosAPI(const std::string& module_name, QObject *parent = nullptr);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Construct with std::string and explicit transport set.
|
|
|
|
|
*/
|
|
|
|
|
LogosAPI(const std::string& module_name, LogosTransportSet transports, QObject *parent = nullptr)
|
|
|
|
|
: LogosAPI(QString::fromStdString(module_name), std::move(transports), parent) {}
|
|
|
|
|
|
|
|
|
|
/* ── per-plugin identity ────────────────────────────────────────────────
|
|
|
|
|
*
|
|
|
|
|
* The ctors above bind this LogosAPI to `TokenManager::forIdentity(name)`,
|
|
|
|
|
* which is `TokenManager::instance()` — the image's ambient ring — unless
|
|
|
|
|
* the name has been isolated. That default is byte-for-byte the old
|
|
|
|
|
* behaviour, and deliberately so: nothing changes for any existing caller.
|
|
|
|
|
*
|
|
|
|
|
* The ctors below are how a HOST that loads several plugins into ONE
|
|
|
|
|
* process gives each of them its own authority. Giving a plugin its own
|
|
|
|
|
* origin STRING does nothing on its own — origin is never consulted on the
|
|
|
|
|
* hot path, which reads the store first and only mints on a miss — so what
|
|
|
|
|
* has to differ is the STORE the plugin presents tokens from.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Construct bound to an EXPLICIT token store.
|
|
|
|
|
*
|
|
|
|
|
* `token_store` is normally `&TokenManager::forIdentity(module_name)` after
|
|
|
|
|
* a successful `TokenManager::isolateIdentity(module_name)`; see
|
|
|
|
|
* LogosAPI::forIdentity(), which packages exactly that.
|
|
|
|
|
*
|
|
|
|
|
* nullptr means "the store for the identity I said I am" —
|
|
|
|
|
* `TokenManager::forIdentity(module_name)` — NOT the ambient singleton, so
|
|
|
|
|
* an isolated identity is honoured even when the caller passes nothing.
|
|
|
|
|
*
|
|
|
|
|
* `parent` is deliberately NOT defaulted here: with a default it would make
|
|
|
|
|
* the existing two-argument call `LogosAPI(name, nullptr)` (basecamp's
|
|
|
|
|
* app/main.cpp does exactly that) ambiguous against
|
|
|
|
|
* LogosAPI(const QString&, QObject*).
|
|
|
|
|
*/
|
|
|
|
|
LogosAPI(const QString& module_name,
|
|
|
|
|
TokenManager* token_store,
|
|
|
|
|
LogosTransportSet transports,
|
|
|
|
|
QObject* parent);
|
|
|
|
|
|
|
|
|
|
/** @brief Explicit token store, default transport set. */
|
|
|
|
|
LogosAPI(const QString& module_name, TokenManager* token_store, QObject* parent)
|
|
|
|
|
: LogosAPI(module_name, token_store, LogosTransportSet{}, parent) {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief A LogosAPI that speaks AS `identity`, from an ISOLATED store.
|
|
|
|
|
*
|
|
|
|
|
* Isolates `identity` (idempotent) and binds the returned object to that
|
2026-08-24 11:16:41 -03:00
|
|
|
* identity's private token store — which is created EMPTY, so its first
|
|
|
|
|
* call to any target must go through `capability_module.requestModule`
|
|
|
|
|
* instead of finding the target's root token lying in the host's ambient
|
|
|
|
|
* ring.
|
|
|
|
|
*
|
|
|
|
|
* THIS IS HALF AN IDENTITY, AND MOST CALLERS WANT logos::admitConsumer
|
|
|
|
|
* (logos_consumer.h) INSTEAD. An empty store cannot authenticate that first
|
|
|
|
|
* requestModule either: something has to install the identity's own
|
|
|
|
|
* host-issued credential under the bootstrap keys, and something has to
|
|
|
|
|
* register that credential with capability_module first. admitConsumer does
|
|
|
|
|
* all of it in the one order that leaves no window. What is left here is the
|
|
|
|
|
* store-selection primitive it is built on.
|
2026-08-19 15:50:49 -03:00
|
|
|
*
|
|
|
|
|
* Returns NULLPTR when the identity cannot be isolated, which happens only
|
|
|
|
|
* if a client for that exact name was already handed the shared store. That
|
|
|
|
|
* is fatal for the identity and must not be papered over by falling back to
|
|
|
|
|
* the host's LogosAPI: one client on the ambient ring and one on the
|
|
|
|
|
* private store is the "looks fixed, isn't" outcome this whole mechanism
|
|
|
|
|
* exists to avoid. Fail the load instead.
|
|
|
|
|
*
|
2026-08-24 11:16:41 -03:00
|
|
|
* Isolating the store is only half of an identity. The host must also mint
|
|
|
|
|
* a credential, make the name a KNOWN CALLER by registering that credential
|
|
|
|
|
* with capability_module (`informModuleToken`), and install it in the
|
|
|
|
|
* identity's store — or the very first `requestModule` presents nothing and
|
|
|
|
|
* is refused. logos::admitConsumer is that operation; this function on its
|
|
|
|
|
* own yields an identity that can call nothing.
|
2026-08-19 15:50:49 -03:00
|
|
|
*/
|
|
|
|
|
static LogosAPI* forIdentity(const QString& identity, QObject* parent = nullptr);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Destructor
|
|
|
|
|
*/
|
|
|
|
|
~LogosAPI();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief The name of the module this LogosAPI belongs to.
|
|
|
|
|
*
|
|
|
|
|
* Every outbound call already carries it (LogosAPIClient's `origin`), it
|
|
|
|
|
* was just never readable from the outside. Generated consumer wrappers
|
|
|
|
|
* need it: a wrapper that reaches the transport through the lp C ABI must
|
|
|
|
|
* name its origin at client-creation time, and the only handle it is given
|
|
|
|
|
* is this object. Reading it here keeps the wrapper's constructor
|
|
|
|
|
* signature — `(LogosAPI*)` / `(LogosAPI*, const QString&)` — unchanged,
|
|
|
|
|
* so no call site moves.
|
|
|
|
|
*/
|
|
|
|
|
QString moduleName() const { return m_module_name; }
|
|
|
|
|
|
2026-08-22 18:37:01 -03:00
|
|
|
/**
|
|
|
|
|
* @brief WHO is calling the dispatch currently running ON THIS THREAD, as
|
|
|
|
|
* the logos-protocol caller document.
|
|
|
|
|
*
|
|
|
|
|
* Returns the JSON object logos-protocol 0.6 defines (normatively in
|
|
|
|
|
* logos_module_impl.h, above logos_module_set_call_caller): a mandatory
|
|
|
|
|
* "kind" of "unknown" | "host" | "module" | "derived" | "operator".
|
|
|
|
|
*
|
|
|
|
|
* Q_INVOKABLE, AND THAT IS THE ENTIRE POINT — this is not a convenience
|
|
|
|
|
* accessor. ModuleProxy::callRemoteMethod opens a logos::CallerScope in the
|
|
|
|
|
* HOST image, into a thread-local that lives in the host's copy of
|
|
|
|
|
* logos-protocol. The generated cdylib glue runs in the MODULE image, which
|
|
|
|
|
* links its OWN copy of both this class and that thread-local, at distinct
|
|
|
|
|
* addresses and with no undefined reference to the host's (Mach-O is
|
|
|
|
|
* TWOLEVEL, PE has no interposition at all; only ELF's flat namespace
|
|
|
|
|
* collapses the two). So the glue cannot call this directly: a direct call
|
|
|
|
|
* binds to the plugin's copy and reads a slot no CallerScope ever wrote —
|
|
|
|
|
* silently empty forever on macOS and Windows, and correct on Linux, which
|
|
|
|
|
* is exactly the asymmetry that let two prior ABI breaks through.
|
|
|
|
|
*
|
|
|
|
|
* QMetaObject::invokeMethod does land in the host image, because it
|
|
|
|
|
* resolves through metaObject()/qt_metacall, which are VIRTUAL and whose
|
|
|
|
|
* vptr was written by the HOST's constructor. It is the same cross-image
|
|
|
|
|
* safe channel initLogos, aboutToUnload and the authToken / hostServices /
|
|
|
|
|
* modulePath properties already use. LogosProviderBase::currentCallerJson()
|
|
|
|
|
* is the one place that performs that call; nothing else should.
|
|
|
|
|
*
|
|
|
|
|
* A RETURN VALUE, not a dynamic property, and that is not a style choice: a
|
|
|
|
|
* property is ONE process-global mutable slot, so two overlapping
|
|
|
|
|
* concurrency:"multi" dispatches from different callers would clobber each
|
|
|
|
|
* other's identity next to an authorization decision. The value here is
|
|
|
|
|
* per-thread and is read synchronously by the thread it belongs to.
|
|
|
|
|
*
|
|
|
|
|
* NEVER EMPTY. logos::currentInboundCallerJson() answers empty for "no
|
|
|
|
|
* dispatch is in flight on this thread", which is a third state distinct
|
|
|
|
|
* from {"kind":"unknown"} ("a dispatch whose caller could not be named").
|
|
|
|
|
* That distinction is meaningful inside the host and cannot cross the C ABI
|
|
|
|
|
* — on that ABI a null document is the POP — so it is collapsed exactly
|
|
|
|
|
* here, at the boundary, using logos-protocol's own producer rather than a
|
|
|
|
|
* second spelling of the same document.
|
|
|
|
|
*/
|
|
|
|
|
Q_INVOKABLE QString currentCallerJson() const;
|
|
|
|
|
|
2026-08-19 15:50:49 -03:00
|
|
|
/**
|
|
|
|
|
* @brief Get the client provider instance
|
|
|
|
|
* @return LogosAPIProvider* Pointer to the provider
|
|
|
|
|
*/
|
|
|
|
|
LogosAPIProvider* getProvider() const;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Get the client instance for communicating with a module
|
|
|
|
|
* @param target_module The module to communicate with
|
|
|
|
|
* @return LogosAPIClient* Pointer to the client
|
|
|
|
|
*/
|
|
|
|
|
LogosAPIClient* getClient(const QString& target_module) const;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Get the client instance — const char* overload (resolves ambiguity)
|
|
|
|
|
*/
|
|
|
|
|
LogosAPIClient* getClient(const char* target_module) const
|
|
|
|
|
{ return getClient(QString(target_module)); }
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Get the client instance for communicating with a module (std::string overload)
|
|
|
|
|
*/
|
|
|
|
|
LogosAPIClient* getClient(const std::string& target_module) const;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Get a client that uses an *explicit* transport instead of
|
|
|
|
|
* the process-global default.
|
|
|
|
|
*
|
|
|
|
|
* Use this when the caller needs to dial one module over a
|
|
|
|
|
* particular protocol without side-effecting the rest of the
|
|
|
|
|
* process. Canonical case: a CLI that talks only to `core_service`
|
|
|
|
|
* over tcp_ssl — using `LogosTransportConfigGlobal::setDefault` for
|
|
|
|
|
* that would also flip the same process's `LogosAPIProvider` into
|
|
|
|
|
* trying to bind a tcp_ssl server, which the CLI has no cert for.
|
|
|
|
|
*
|
|
|
|
|
* Cached per (target_module, full LogosTransportConfig) — repeat
|
|
|
|
|
* calls with the same target *and* the same transport return the
|
|
|
|
|
* same client. The cache key covers every config field that can
|
|
|
|
|
* distinguish two clients (protocol, host, port, codec, all TLS
|
|
|
|
|
* settings), via the operator== / qHash defined alongside
|
|
|
|
|
* LogosTransportConfig, so two callers with different TLS or codec
|
|
|
|
|
* settings always get separate clients — no risk of silently
|
|
|
|
|
* reusing an insecure connection where a secure one was asked for.
|
|
|
|
|
*/
|
|
|
|
|
LogosAPIClient* getClient(const QString& target_module,
|
|
|
|
|
const LogosTransportConfig& transport) const;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Get the token manager instance
|
|
|
|
|
* @return TokenManager* Pointer to the token manager
|
|
|
|
|
*/
|
|
|
|
|
TokenManager* getTokenManager() const;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Set the transport used by the SDK's auto-`requestModule`
|
|
|
|
|
* token-fetch flow (inside LogosAPIClient::invokeRemoteMethod{,Async}).
|
|
|
|
|
*
|
|
|
|
|
* That flow always dials `capability_module` to fetch a per-target
|
|
|
|
|
* token, regardless of which module is the actual call target.
|
|
|
|
|
* Without an explicit transport it falls through to
|
|
|
|
|
* LogosTransportConfigGlobal::getDefault() (LocalSocket), which
|
|
|
|
|
* times out 20 s when capability_module is reachable only on TCP
|
|
|
|
|
* (e.g. CLI on host, daemon in container).
|
|
|
|
|
*
|
|
|
|
|
* Callers that have read the daemon's per-module advertised
|
|
|
|
|
* transports (e.g. from logoscore's daemon.json) should register
|
|
|
|
|
* capability_module's transport here so getClient builds each
|
|
|
|
|
* LogosAPIClient with the right capability_consumer.
|
|
|
|
|
*
|
|
|
|
|
* The setting only affects clients constructed *after* this call
|
|
|
|
|
* — clients already in the cache keep whatever capability transport
|
|
|
|
|
* they were built with.
|
|
|
|
|
*/
|
|
|
|
|
void setCapabilityModuleTransport(const LogosTransportConfig& transport);
|
|
|
|
|
|
|
|
|
|
using QObject::setProperty;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Set a dynamic property from a UTF-8 std::string (delegates to QVariant + QString).
|
|
|
|
|
*/
|
|
|
|
|
bool setProperty(const char* name, const std::string& value);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
QString m_module_name;
|
|
|
|
|
LogosAPIProvider* m_provider;
|
|
|
|
|
// Single cache for both getClient overloads. Keyed by a
|
|
|
|
|
// mode-aware composite (LogosAPIClientCacheKey above) so that:
|
|
|
|
|
// - Mock/Local mode buckets ignore transport (the factory does too)
|
|
|
|
|
// - Remote mode keys include the full LogosTransportConfig
|
|
|
|
|
// - the no-transport overload resolves to the same key as an
|
|
|
|
|
// explicit caller passing LogosTransportConfigGlobal::getDefault()
|
|
|
|
|
// - mode switches don't return stale clients from the previous mode
|
|
|
|
|
mutable QHash<LogosAPIClientCacheKey, LogosAPIClient*> m_clients;
|
|
|
|
|
TokenManager* m_token_manager;
|
|
|
|
|
// ABI note: this private layout is consumed by every plugin that
|
|
|
|
|
// statically links the host runtime (the `logos_qt_host` archive this
|
|
|
|
|
// directory builds; it was called libsdk before the SDK split, and the
|
|
|
|
|
// same note still stands in logos-protocol's logos_api_client.h).
|
|
|
|
|
// Inserting a field above m_token_manager
|
|
|
|
|
// shifts its offset and SILENTLY breaks plugins compiled before
|
|
|
|
|
// the change — they read garbage where m_token_manager used to
|
|
|
|
|
// live, getClient() then constructs LogosAPIClients with a bogus
|
|
|
|
|
// TokenManager*, and the first cross-process call segfaults.
|
|
|
|
|
// Append new private members at the END only. (Long-term cure:
|
|
|
|
|
// pimpl this class so sizeof / offsets become opaque.)
|
|
|
|
|
//
|
|
|
|
|
// Optional override for the capability_module transport used by
|
|
|
|
|
// each LogosAPIClient's pre-built m_capability_consumer. Set via
|
|
|
|
|
// setCapabilityModuleTransport(). nullopt = use the global default.
|
|
|
|
|
std::optional<LogosTransportConfig> m_capabilityModuleTransport;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#endif // LOGOS_API_H
|