Qt-split retarget + logos_protocol_version load gate (#142)

* Qt-split retarget + protocol-version load gate

- Link the split SDK stack: logos-qt-sdk (LogosAPI/provider glue; the
  logos_sdk alias now points at logos-qt-sdk::logos_qt_sdk, chaining
  logos-protocol) + Qt-free logos-cpp-sdk headers.
- Protocol-version load gate (the first real consumer of module
  metadata pre-load): ModuleManager reads the module's embedded
  logos_protocol_version before runtime.load() and applies the one
  compatibility rule — equal protocol MAJOR loads, different MAJOR is
  refused with a diagnostic naming both versions, missing/unparseable
  stamp (pre-protocol modules) loads permissively with a warning. The
  decision logic is std-only (logos_core/protocol_gate.h) and unit
  tested (refuse bumped major / warn-load legacy / silent minor skew).
- ModuleDescriptor.rawMetadata is now actually populated for runtimes.

* lock: pin extraction-chain branch revs for standalone CI

Temporary — drop when the chain PRs merge (re-lock against masters).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* doctest: pin logoscore-cli to its qt-split branch head

The doc-test builds logoscore-cli at latest master with only liblogos
overridden to the commit under test; master logoscore-cli cannot build
against qt-split liblogos. Pin the runtime to the chain branch
(logos-co/logos-logoscore-cli#43) so the doc-test exercises the
coherent stack. Temporary — revert to the unpinned URL when the chain
merges.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* host: surface the spawn auth token as a LogosAPI property

cdylib-authored modules run their own statically-linked protocol stack
whose TokenManager is a separate copy of the singleton; the generated Qt
glue reads this property (cross-image-safe, like modulePath) and seeds
the cdylib's stack via logos_module_accept_token so the module's
outbound calls authenticate.

* host: set the authToken property before registerObject

registerObject runs the provider object's init() — where the cdylib glue
reads the property. Setting it afterwards meant cdylib modules always saw
an empty token.

* lock: protocol+cpp-sdk merged to master — pins advance (protocol 9de4165, cpp-sdk f0fe8cb, qt-sdk 722e590)

* lock: qt-sdk#1 merged — pin advances to qt-sdk master

* gate: drop QJson from the Qt-free core — parse rawMetadataJson with nlohmann

The protocol-version load gate had pulled QJsonDocument/QJsonObject into
src/logos_core (Qt-free territory). logos-module now exposes the embedded
metadata as a compact JSON string, so the gate reads it via nlohmann and
the std::string extractMetadata overload.

* lock: logos-module b42805d (result-lm untracked)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dario Lipicar
2026-06-12 21:45:49 -03:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 66256b54ef
commit 050f2d3628
12 changed files with 449 additions and 5338 deletions
+2 -2
View File
@@ -58,9 +58,9 @@ sections:
> tested. With no pin it falls back to latest `master`.
steps:
- title: "Build the CLI with the liblogos override"
run: "nix build 'github:logos-co/logos-logoscore-cli' --override-input logos-liblogos 'github:logos-co/logos-liblogos{release}' --out-link ./logos"
run: "nix build 'github:logos-co/logos-logoscore-cli/b92ade06cdbd3cdf48c8de5b8375cbcc3a6088cf' --override-input logos-liblogos 'github:logos-co/logos-liblogos{release}' --out-link ./logos"
code_block: |
nix build 'github:logos-co/logos-logoscore-cli' \
nix build 'github:logos-co/logos-logoscore-cli/b92ade06cdbd3cdf48c8de5b8375cbcc3a6088cf' \
--override-input logos-liblogos 'github:logos-co/logos-liblogos' \
--out-link ./logos
check_file: "logos/bin/logoscore"
Generated
+187 -5305
View File
File diff suppressed because it is too large Load Diff
+13 -6
View File
@@ -5,13 +5,18 @@
logos-nix.url = "github:logos-co/logos-nix";
nixpkgs.follows = "logos-nix/nixpkgs";
logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk";
logos-cpp-sdk.inputs.logos-protocol.follows = "logos-protocol";
logos-protocol.url = "github:logos-co/logos-protocol";
logos-qt-sdk.url = "github:logos-co/logos-qt-sdk";
logos-qt-sdk.inputs.logos-protocol.follows = "logos-protocol";
logos-qt-sdk.inputs.logos-cpp-sdk.follows = "logos-cpp-sdk";
logos-capability-module.url = "github:logos-co/logos-capability-module";
logos-module.url = "github:logos-co/logos-module";
process-stats.url = "github:logos-co/process-stats";
logos-package-manager.url = "github:logos-co/logos-package-manager";
};
outputs = { self, nixpkgs, logos-nix, logos-cpp-sdk, logos-capability-module, logos-module, logos-package-manager, process-stats }:
outputs = { self, nixpkgs, logos-nix, logos-cpp-sdk, logos-protocol, logos-qt-sdk, logos-capability-module, logos-module, logos-package-manager, process-stats }:
let
systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ];
@@ -19,6 +24,8 @@
inherit system;
pkgs = import nixpkgs { inherit system; };
logosSdk = logos-cpp-sdk.packages.${system}.default;
logosProtocolPkg = logos-protocol.packages.${system}.default;
logosQtSdk = logos-qt-sdk.packages.${system}.default;
capabilityModule = logos-capability-module.packages.${system}.default;
logosModule = logos-module.packages.${system}.default;
processStats = process-stats.packages.${system}.default;
@@ -27,12 +34,12 @@
});
in
{
packages = forAllSystems ({ pkgs, system, logosSdk, capabilityModule, logosModule, processStats, logosPackageManager, logosPackageManagerPortable }:
packages = forAllSystems ({ pkgs, system, logosSdk, logosProtocolPkg, logosQtSdk, capabilityModule, logosModule, processStats, logosPackageManager, logosPackageManagerPortable }:
let
# Common configuration (dev, default)
common = import ./nix/default.nix { inherit pkgs logosSdk logosModule processStats logosPackageManager; };
common = import ./nix/default.nix { inherit pkgs logosSdk logosProtocolPkg logosQtSdk logosModule processStats logosPackageManager; };
# Common configuration (portable)
commonPortable = import ./nix/default.nix { inherit pkgs logosSdk logosModule processStats; logosPackageManager = logosPackageManagerPortable; portableBuild = true; };
commonPortable = import ./nix/default.nix { inherit pkgs logosSdk logosProtocolPkg logosQtSdk logosModule processStats; logosPackageManager = logosPackageManagerPortable; portableBuild = true; };
src = ./.;
# Shared build that compiles everything (dev)
@@ -46,13 +53,13 @@
modules = import ./nix/modules.nix { inherit pkgs common capabilityModule; };
modulesPortable = import ./nix/modules.nix { inherit pkgs capabilityModule; common = commonPortable; portableBuild = true; };
bin = import ./nix/bin.nix { inherit pkgs common build lib modules; };
include = import ./nix/include.nix { inherit pkgs common src logosSdk; };
include = import ./nix/include.nix { inherit pkgs common src logosSdk; inherit logosProtocolPkg logosQtSdk; };
tests = import ./nix/tests.nix { inherit pkgs common build; };
# Portable package components
libPortable = import ./nix/lib.nix { inherit pkgs; common = commonPortable; build = buildPortable; };
binPortable = import ./nix/bin.nix { inherit pkgs; common = commonPortable; build = buildPortable; lib = libPortable; modules = modulesPortable; };
includePortable = import ./nix/include.nix { inherit pkgs src logosSdk; common = commonPortable; };
includePortable = import ./nix/include.nix { inherit pkgs src logosSdk; inherit logosProtocolPkg logosQtSdk; common = commonPortable; };
# Combined package (dev)
liblogos = pkgs.symlinkJoin {
+7 -1
View File
@@ -1,5 +1,5 @@
# Common build configuration shared across all packages
{ pkgs, logosSdk, logosModule, processStats, logosPackageManager, portableBuild ? false }:
{ pkgs, logosSdk, logosProtocolPkg, logosQtSdk, logosModule, processStats, logosPackageManager, portableBuild ? false }:
{
pname = "logos-liblogos";
@@ -28,6 +28,8 @@
pkgs.qt6.qtbase
pkgs.qt6.qtremoteobjects
logosSdk
logosProtocolPkg
logosQtSdk
pkgs.zstd
pkgs.gtest
pkgs.cli11
@@ -41,6 +43,8 @@
cmakeFlags = [
"-GNinja"
"-DLOGOS_CPP_SDK_ROOT=${logosSdk}"
"-DLOGOS_PROTOCOL_ROOT=${logosProtocolPkg}"
"-DLOGOS_QT_SDK_ROOT=${logosQtSdk}"
"-DLOGOS_MODULE_ROOT=${logosModule}"
"-DPROCESS_STATS_ROOT=${processStats}"
"-DLOGOS_PACKAGE_MANAGER_ROOT=${logosPackageManager}"
@@ -51,6 +55,8 @@
# Environment variables
env = {
LOGOS_CPP_SDK_ROOT = "${logosSdk}";
LOGOS_PROTOCOL_ROOT = "${logosProtocolPkg}";
LOGOS_QT_SDK_ROOT = "${logosQtSdk}";
LOGOS_MODULE_ROOT = "${logosModule}";
PROCESS_STATS_ROOT = "${processStats}";
LOGOS_PACKAGE_MANAGER_ROOT = "${logosPackageManager}";
+7 -1
View File
@@ -1,5 +1,5 @@
# Installs the logos-liblogos headers
{ pkgs, common, src, logosSdk ? null }:
{ pkgs, common, src, logosSdk ? null, logosProtocolPkg ? null, logosQtSdk ? null }:
pkgs.stdenv.mkDerivation {
pname = "${common.pname}-headers";
@@ -27,6 +27,12 @@ pkgs.stdenv.mkDerivation {
if [ -n "${toString logosSdk}" ] && [ -d "${toString logosSdk}/include" ]; then
cp -r ${toString logosSdk}/include/* $out/include/ 2>/dev/null || true
fi
if [ -n "${toString logosProtocolPkg}" ] && [ -d "${toString logosProtocolPkg}/include" ]; then
cp -r ${toString logosProtocolPkg}/include/* $out/include/ 2>/dev/null || true
fi
if [ -n "${toString logosQtSdk}" ] && [ -d "${toString logosQtSdk}/include" ]; then
cp -r ${toString logosQtSdk}/include/* $out/include/ 2>/dev/null || true
fi
runHook postInstall
'';
+2
View File
@@ -28,6 +28,8 @@ pkgs.stdenv.mkDerivation {
cmake -B build -S ${build.src} \
-GNinja \
-DLOGOS_CPP_SDK_ROOT=${common.env.LOGOS_CPP_SDK_ROOT} \
-DLOGOS_PROTOCOL_ROOT=${common.env.LOGOS_PROTOCOL_ROOT} \
-DLOGOS_QT_SDK_ROOT=${common.env.LOGOS_QT_SDK_ROOT} \
-DLOGOS_MODULE_ROOT=${common.env.LOGOS_MODULE_ROOT} \
-DPROCESS_STATS_ROOT=${common.env.PROCESS_STATS_ROOT} \
-DLOGOS_PACKAGE_MANAGER_ROOT=${common.env.LOGOS_PACKAGE_MANAGER_ROOT} \
+35 -20
View File
@@ -7,30 +7,45 @@ endif()
get_filename_component(LOGOS_CPP_SDK_ROOT "${LOGOS_CPP_SDK_ROOT}" ABSOLUTE)
# Check if we have a built SDK package (with lib and include directories)
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/lib" AND EXISTS "${LOGOS_CPP_SDK_ROOT}/include")
# Use the built SDK package via its CMake Config file. The SDK ships
# logos-cpp-sdkConfig.cmake under lib/cmake/logos-cpp-sdk/ whose
# imported target (logos-cpp-sdk::logos_sdk) carries OpenSSL / Boost
# / nlohmann_json / Qt on its INTERFACE_LINK_LIBRARIES — no manual
# wiring required here.
message(STATUS "Using built logos-cpp-sdk at ${LOGOS_CPP_SDK_ROOT}")
find_package(logos-cpp-sdk REQUIRED
PATHS "${LOGOS_CPP_SDK_ROOT}" NO_DEFAULT_PATH)
# Provide a `logos_sdk` alias so the rest of this CMakeLists keeps
# using the unqualified name (matches the source-layout behaviour
# where `logos_sdk` is the target name from add_library).
# The SDK stack since the Qt split: logos-protocol (transports + lp_* C
# ABI), logos-qt-sdk (LogosAPI + provider glue — what this runtime's Qt
# side uses), logos-cpp-sdk (Qt-free std headers). The `logos_sdk` alias
# is kept so the rest of this file (and the host targets) stay unchanged;
# it now points at logos-qt-sdk, whose link interface chains
# logos-protocol and its OpenSSL/Boost/nlohmann deps.
if(NOT DEFINED LOGOS_PROTOCOL_ROOT AND DEFINED ENV{LOGOS_PROTOCOL_ROOT})
set(LOGOS_PROTOCOL_ROOT "$ENV{LOGOS_PROTOCOL_ROOT}")
endif()
if(NOT DEFINED LOGOS_QT_SDK_ROOT AND DEFINED ENV{LOGOS_QT_SDK_ROOT})
set(LOGOS_QT_SDK_ROOT "$ENV{LOGOS_QT_SDK_ROOT}")
endif()
if(EXISTS "${LOGOS_PROTOCOL_ROOT}/lib/cmake/logos-protocol"
AND EXISTS "${LOGOS_QT_SDK_ROOT}/lib/cmake/logos-qt-sdk")
message(STATUS "Using logos-protocol at ${LOGOS_PROTOCOL_ROOT}")
message(STATUS "Using logos-qt-sdk at ${LOGOS_QT_SDK_ROOT}")
find_package(logos-protocol REQUIRED
PATHS "${LOGOS_PROTOCOL_ROOT}/lib/cmake/logos-protocol" NO_DEFAULT_PATH)
find_package(logos-qt-sdk REQUIRED
PATHS "${LOGOS_QT_SDK_ROOT}/lib/cmake/logos-qt-sdk" NO_DEFAULT_PATH)
if(NOT TARGET logos_sdk)
add_library(logos_sdk INTERFACE IMPORTED)
target_link_libraries(logos_sdk INTERFACE logos-cpp-sdk::logos_sdk)
target_link_libraries(logos_sdk INTERFACE logos-qt-sdk::logos_qt_sdk)
endif()
elseif(EXISTS "${LOGOS_CPP_SDK_ROOT}/cpp/CMakeLists.txt")
# Use the source SDK (original behavior)
message(STATUS "Using logos-cpp-sdk source at ${LOGOS_CPP_SDK_ROOT}")
add_subdirectory(${LOGOS_CPP_SDK_ROOT}/cpp ${CMAKE_BINARY_DIR}/sdk)
else()
message(FATAL_ERROR "logos-cpp-sdk not found at ${LOGOS_CPP_SDK_ROOT}")
message(FATAL_ERROR "logos-protocol / logos-qt-sdk not found. Set "
"LOGOS_PROTOCOL_ROOT and LOGOS_QT_SDK_ROOT to built packages.")
endif()
# Qt-free base SDK headers (logos_module_context.h etc.) — header-only.
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/lib/cmake/logos-cpp-sdk")
find_package(logos-cpp-sdk REQUIRED
PATHS "${LOGOS_CPP_SDK_ROOT}/lib/cmake/logos-cpp-sdk" NO_DEFAULT_PATH)
if(NOT TARGET logos_headers)
add_library(logos_headers INTERFACE IMPORTED)
target_link_libraries(logos_headers INTERFACE logos-cpp-sdk::logos_headers)
endif()
target_link_libraries(logos_sdk INTERFACE logos-cpp-sdk::logos_headers)
endif()
# Find logos-module library
+43
View File
@@ -19,6 +19,9 @@
#include <boost/uuid/uuid_io.hpp>
#include "logos_api.h"
#include "logos_api_client.h"
#include "logos_module.h"
#include "logos_protocol.h"
#include "protocol_gate.h"
#include "logos_transport_config_json.h"
#include "token_manager.h"
#include "instance_persistence.h"
@@ -261,6 +264,46 @@ namespace {
desc.transportSetJson = it->second;
}
// ── Protocol-version load gate ─────────────────────────────────
// Read the module's embedded metadata without loading it and apply
// the one compatibility rule: equal logos-protocol MAJOR loads,
// different MAJOR is refused, a missing stamp (pre-protocol module)
// loads permissively with a warning.
std::string moduleProtocolVersion;
if (auto meta = ModuleLib::LogosModule::extractMetadata(modPath)) {
// While we have it, hand the full metadata to the runtime.
desc.rawMetadata = nlohmann::json::parse(
meta->rawMetadataJson, nullptr, /*allow_exceptions=*/false);
if (desc.rawMetadata.is_discarded())
desc.rawMetadata = nlohmann::json::object();
if (auto it = desc.rawMetadata.find("logos_protocol_version");
it != desc.rawMetadata.end() && it->is_string())
moduleProtocolVersion = it->get<std::string>();
}
const auto gate = LogosCore::evaluateProtocolGate(
moduleProtocolVersion, LOGOS_PROTOCOL_VERSION_MAJOR);
switch (gate.decision) {
case LogosCore::ProtocolGateDecision::Refuse:
spdlog::error(
"Refusing to load module {}: built against logos-protocol {} "
"(major {}), this host speaks major {} ({}) — incompatible "
"protocol majors",
name, moduleProtocolVersion, gate.moduleMajor,
LOGOS_PROTOCOL_VERSION_MAJOR, LOGOS_PROTOCOL_VERSION_STRING);
return false;
case LogosCore::ProtocolGateDecision::AllowLegacy:
spdlog::warn(
"Module {} carries no usable logos_protocol_version "
"(pre-protocol build) — loading permissively",
name);
break;
case LogosCore::ProtocolGateDecision::Allow:
spdlog::debug("Module {} protocol version {} compatible with host {}",
name, moduleProtocolVersion,
LOGOS_PROTOCOL_VERSION_STRING);
break;
}
auto rt = runtimeRegistry().select(desc);
if (!rt) {
spdlog::warn("No runtime available to load module: {}", name);
+59
View File
@@ -0,0 +1,59 @@
#ifndef LOGOS_PROTOCOL_GATE_H
#define LOGOS_PROTOCOL_GATE_H
#include <cstdlib>
#include <string>
// ---------------------------------------------------------------------------
// Protocol-version load gate (pure decision logic, std-only).
//
// Every module built by a current logos-module-builder carries the
// logos-protocol semver it was compiled against in its embedded metadata
// (`logos_protocol_version`). One rule governs Logos load/call
// compatibility: two participants interoperate iff they share the same
// protocol MAJOR. The host (this library) refuses to load a module whose
// major differs from its own; modules with no stamp predate the scheme and
// load permissively ("legacy") so the existing fleet never hard-fails.
//
// The caller (ModuleManager::loadModuleInternal) extracts the stamp from the
// plugin metadata pre-load and logs according to the decision.
// ---------------------------------------------------------------------------
namespace LogosCore {
enum class ProtocolGateDecision {
Allow, // same major — compatible
AllowLegacy, // no/unparseable stamp — pre-protocol module, load + warn
Refuse, // different major — incompatible, do not load
};
struct ProtocolGateResult {
ProtocolGateDecision decision;
int moduleMajor = -1; // -1 when absent/unparseable
};
// Parse the MAJOR component of "MAJOR.MINOR.PATCH". Returns -1 when the
// string does not start with a non-negative integer.
inline int protocolVersionMajor(const std::string& version)
{
if (version.empty()) return -1;
char* end = nullptr;
const long major = std::strtol(version.c_str(), &end, 10);
if (end == version.c_str() || major < 0) return -1;
return static_cast<int>(major);
}
inline ProtocolGateResult evaluateProtocolGate(const std::string& moduleVersion,
int hostMajor)
{
const int moduleMajor = protocolVersionMajor(moduleVersion);
if (moduleMajor < 0)
return {ProtocolGateDecision::AllowLegacy, -1};
if (moduleMajor == hostMajor)
return {ProtocolGateDecision::Allow, moduleMajor};
return {ProtocolGateDecision::Refuse, moduleMajor};
}
} // namespace LogosCore
#endif // LOGOS_PROTOCOL_GATE_H
@@ -75,6 +75,14 @@ LogosAPI* initializeLogosAPI(const std::string& moduleName, QObject* module,
fs::path(instancePersistencePath).filename().string());
}
// Surface the token as a QObject property BEFORE registerObject:
// registration runs the provider object's init(), where cdylib-authored
// modules read this property (a cross-image-safe dynamic lookup, like
// modulePath above) and forward it across the module-impl C ABI via
// logos_module_accept_token — their statically-linked protocol stack has
// its own TokenManager copy the host's saveToken calls below never reach.
logos_api->setProperty("authToken", QString::fromStdString(authToken));
bool success = logos_api->getProvider()->registerObject(basePlugin->name(), module);
if (success) {
logos_api->getTokenManager()->saveToken(std::string("core"), authToken);
+17 -3
View File
@@ -21,6 +21,7 @@ add_executable(logos_core_tests
test_module_name_validation.cpp
test_runtime_registry.cpp
test_module_runtime_abstraction.cpp
test_protocol_gate.cpp
# qt_test_adapter.h calls SubprocessTokenReceiver::receive; compile the
# implementation unit directly into the test binary (it lives in logos_host_qt,
# which is an executable, not a linkable library).
@@ -54,15 +55,28 @@ if(DEFINED PROCESS_STATS_ROOT AND EXISTS "${PROCESS_STATS_ROOT}/include")
target_include_directories(logos_core_tests PRIVATE ${PROCESS_STATS_ROOT}/include)
endif()
# Add SDK include directory (same logic as src/CMakeLists.txt)
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/lib" AND EXISTS "${LOGOS_CPP_SDK_ROOT}/include")
# Add SDK include directories (same layering as src/CMakeLists.txt:
# Qt-free base headers + qt-sdk developer layer + protocol layer)
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/include")
target_include_directories(logos_core_tests PRIVATE
${LOGOS_CPP_SDK_ROOT}/include
${LOGOS_CPP_SDK_ROOT}/include/cpp
${LOGOS_CPP_SDK_ROOT}/include/core
)
else()
target_include_directories(logos_core_tests PRIVATE ${LOGOS_CPP_SDK_ROOT}/cpp)
endif()
if(DEFINED LOGOS_QT_SDK_ROOT AND EXISTS "${LOGOS_QT_SDK_ROOT}/include")
target_include_directories(logos_core_tests PRIVATE
${LOGOS_QT_SDK_ROOT}/include
${LOGOS_QT_SDK_ROOT}/include/cpp
${LOGOS_QT_SDK_ROOT}/include/core
)
endif()
if(DEFINED LOGOS_PROTOCOL_ROOT AND EXISTS "${LOGOS_PROTOCOL_ROOT}/include")
target_include_directories(logos_core_tests PRIVATE
${LOGOS_PROTOCOL_ROOT}/include
${LOGOS_PROTOCOL_ROOT}/include/cpp
)
endif()
gtest_discover_tests(logos_core_tests)
+69
View File
@@ -0,0 +1,69 @@
#include <gtest/gtest.h>
#include "logos_core/protocol_gate.h"
#include "logos_protocol.h"
// Gate tests (land with the gate, per the versioning design):
// (a) a module stamped with a different MAJOR is refused,
// (b) an unstamped (legacy) module is allowed permissively,
// (c) equal major / different minor loads silently.
// The stamp values are exercised against both a fixed major and the real
// LOGOS_PROTOCOL_VERSION_MAJOR the host links.
using LogosCore::evaluateProtocolGate;
using LogosCore::protocolVersionMajor;
using LogosCore::ProtocolGateDecision;
TEST(ProtocolGate, RefusesDifferentMajor)
{
const auto r = evaluateProtocolGate("2.0.0", 1);
EXPECT_EQ(r.decision, ProtocolGateDecision::Refuse);
EXPECT_EQ(r.moduleMajor, 2);
// Against the real host major: a deliberately bumped stamp is refused.
const int hostMajor = LOGOS_PROTOCOL_VERSION_MAJOR;
const std::string bumped = std::to_string(hostMajor + 1) + ".0.0";
EXPECT_EQ(evaluateProtocolGate(bumped, hostMajor).decision,
ProtocolGateDecision::Refuse);
}
TEST(ProtocolGate, MissingStampLoadsPermissively)
{
const auto r = evaluateProtocolGate("", LOGOS_PROTOCOL_VERSION_MAJOR);
EXPECT_EQ(r.decision, ProtocolGateDecision::AllowLegacy);
EXPECT_EQ(r.moduleMajor, -1);
}
TEST(ProtocolGate, UnparseableStampLoadsPermissively)
{
EXPECT_EQ(evaluateProtocolGate("garbage", 1).decision,
ProtocolGateDecision::AllowLegacy);
EXPECT_EQ(evaluateProtocolGate("-1.0.0", 1).decision,
ProtocolGateDecision::AllowLegacy);
}
TEST(ProtocolGate, EqualMajorDifferentMinorAllowsSilently)
{
const int hostMajor = LOGOS_PROTOCOL_VERSION_MAJOR;
const std::string newerMinor =
std::to_string(hostMajor) + ".99.7";
const auto r = evaluateProtocolGate(newerMinor, hostMajor);
EXPECT_EQ(r.decision, ProtocolGateDecision::Allow);
EXPECT_EQ(r.moduleMajor, hostMajor);
}
TEST(ProtocolGate, HostOwnVersionAllows)
{
EXPECT_EQ(evaluateProtocolGate(LOGOS_PROTOCOL_VERSION_STRING,
LOGOS_PROTOCOL_VERSION_MAJOR).decision,
ProtocolGateDecision::Allow);
}
TEST(ProtocolGate, MajorParser)
{
EXPECT_EQ(protocolVersionMajor("0.1.0"), 0);
EXPECT_EQ(protocolVersionMajor("12.3.4"), 12);
EXPECT_EQ(protocolVersionMajor("1"), 1);
EXPECT_EQ(protocolVersionMajor(""), -1);
EXPECT_EQ(protocolVersionMajor("x.y.z"), -1);
}