* fix(cdylib): typed scalars go through the codec, and the codec checks signedness
The cdylib dispatch decoded composites with the generated codec but scalars with
a bare nlohmann accessor. Two silent conversions lived in that gap:
echoUint(-1) -> 18446744073709551615 (.get<uint64_t>() wraps)
echoInt(3.7) -> 3 (.get<int64_t>() truncates)
The Rust provider rejects both. So a contract both providers share answered
differently depending on which one a consumer resolved to, and one of the two
answers was a sign flip on a nominal value.
The reason this was left in place was circular, and it was written in the source:
the leniency "is pinned by the conformance matrix (`hostile/int/fractional`
expects 3 from 3.7 on this provider)". Those cells exist to DOCUMENT the
divergence — their own `why` text says the strict behaviour is correct. The
expectations moved with this change.
TWO sites, because fixing one relocates the bug rather than closing it:
* jsonArgToStd no longer special-cases int/uint/float64/bool/tstr — everything
typed goes through Codec<T>. `any` still passes through, since it declares
nothing to check against; bstr keeps its tagged-bytes decoder.
* the EMITTED codec (this generator writes its own copy into <name>_types.h,
separate from logos_codec.h) gated integers on `is_number()`, which admits
floats AND negatives. Routing scalars into it without fixing it would have
changed nothing. The integer specializations are now spelled out rather than
driven from the scalar table, because a category check is not enough for them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: bump logos-protocol to the signedness + sentinel fixes
logos-protocol c0df466 (#31):
* Codec<T> checks integer signedness and range, so a negative can no longer
wrap into an unsigned and a wide value can no longer truncate.
* the pending-call sentinel is matched by shape rather than key presence, so
a user map merely carrying that key no longer hangs the call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
logos-protocol 8b8a358 (#30) — two places where a uint64 above int64max stopped
being itself:
* the universal -> Qt EVENT bridge converted with QJsonDocument::fromJson +
QJsonValue::toVariant instead of the canonical helper the method path uses,
so uintEvent(2^64-1) arrived as 1.8446744073709552e+19 while the equivalent
method return was exact. The same bridge also failed to decode canonical
tagged bytes into a QByteArray.
* the plain (tcp/tcp_ssl) wire had no unsigned alternative in RpcValue, so the
same value wrapped to -1 — silently, and independently in each direction.
Retires M6 from the LIDL conformance matrix.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Picks up logos-protocol ae2f7e1: lp clients on a Qt-affine transport are
constructed on the Qt main thread rather than on whichever thread makes the
module's first outbound call, which previously left the QtRO node and socket on
a thread with no event loop (every replica acquire then burned its full 20s
timeout and returned an empty result).
Hygiene only for this repo: `LpClient::ensure()` here already creates the client
lazily and that stays as it is — the fix is entirely inside the protocol's
lp_client_create. Module builds take their protocol from logos-module-builder's
`follows`, so they do not depend on this pin; this keeps cpp-sdk's own lock and
its tests on the same protocol as the rest of the stack.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Picks up logos-protocol#27 (8ede8ec), which stops lp_client_destroy from
deleting the LogosAPIClient on whatever thread released the last handle
share. Destroying it off the owner thread tore the QtRO transport's socket
notifiers down cross-thread and took the module process out with SIGSEGV;
the destroy now defers to the owner via deleteLater(). Reproduced and fixed
end-to-end under logoscore, 3/3 each way, with a control arm isolating that
commit as the cause.
This bump also crosses protocol#20 (group-shareable local sockets,
stale-socket reaper, bind-failure detection) — this repo's pin was one
release behind the other SDKs.
Checks green: logos-cpp-sdk-tests.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Picks up logos-protocol#26 (ef24bd7): provider returns the structured
unauthorized sentinel on a stale token; consumer drops it, re-runs
requestModule and retries once. Standalone `nix flake check`: 168/168 pass.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Picks up logos-co/logos-protocol#24 (664b43f): LogosAPIConsumer caches the
remote-object handle per name across sync + async calls, avoiding a per-call
QtRO replica acquire. Consumer-side, ABI-compatible (appended virtual/member,
C ABI untouched).
Picks up logos-protocol 976bc7a (logos-co/logos-protocol#10), which stops
ModuleProxy::callRemoteMethod from logging call arguments in plaintext.
d5d58e7 (current pin) is the exact parent, so this is a one-commit bump.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat: emit logos_module_dispatch_async for concurrency:multi C++ modules
The cdylib C-ABI exports gain an async dispatch entry (each call run on a worker
thread, reply on completion) for universal + cdylib C++ modules. --concurrency
multi flag in logos-cpp-generator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: cpp-sdk concurrent-dispatch doctest (concurrency:"multi" showcase)
A concurrency:multi C++ worker + a single driver firing concurrent calls, showing
the multi worker overlaps them. The C++ cdylib generator needs NO change — its
logos_module_dispatch is already safe to call concurrently; the worker pool lives
in the Qt glue and the result is deferred via a sentinel + completion event.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: wire universal-cdylib modules() independent of the context latch
A C++ interface:"universal" cdylib that calls another module via
modules().<dep>... segfaulted on its FIRST cross-module call: the typed
dependency surface (LogosModules) was wired inside lidlTryFireContext, which
returns early when no persistence context was stored (g_ctxStored == false).
When the daemon never delivers a context (observed: zero set_context calls for
a context-less module), maybeSetLogosModules never ran, m_logosModulesPtr
stayed null, and LogosModuleContext::modules() dereferenced null.
modules() does not need the context — each dependency client bakes its
target+origin at codegen time and creates its lp client lazily on first call.
So wire it in its own context-independent once-latch (lidlEnsureModulesWired),
called at the top of lidlTryFireContext before the context-gated early return,
i.e. on the first dispatch / set_context / set_emit_callback. A module with
deps but no stored context now has modules() wired before any handler runs.
(Bump the concurrent-dispatch doctest's post-daemon-start sleep 3 -> 6 to match
the rust spec's cold-start margin.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: green the universal-cdylib concurrent-dispatch doctest + wire into CI
The driver/worker split a declarations-only impl header (so the cpp-generator's
--header-to-lidl doesn't choke on inline std calls) from the impl body. That
body was never compiled — metadata's nix.cmake.extra_sources is parsed but not
consumed by the LogosModule.cmake the build actually uses — so the impl symbols
(FanoutDriverModuleImpl::fanOut / ::peak) were UNDEFINED in the dylib and the
plugin null-jumped (bl -> 0x0) on the first cross-module call. Pass the impl
.cpp via logos_module()'s existing SOURCES argument so it's compiled and linked.
With this the cpp universal-cdylib reaches worker peak overlap 4 end-to-end (a
single-threaded driver fans out 4 async calls into a concurrency:"multi" worker
and all four overlap), matching the Rust half. Wire the spec into doctests.yml
so the workspace pipeline runs it.
(Auto-wiring metadata.extra_sources — so the split pattern works without listing
SOURCES by hand — needs the consumer added to the backend LogosModule.cmake
copies in logos-plugin-core / logos-plugin-qt; tracked separately.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: bump logos-protocol to merged master (protocol#5)
logos-protocol 9de4165 → 4ea32a3 (concurrent-dispatch handshake coalescing, now on master)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: cpp-generator consumes logos-lidl; delete embedded frontend
The canonical LIDL frontend now lives in logos-lidl. cpp-generator links it
and keeps only the C++/Qt-specific parts (impl-header parsing, the gen_client/
gen_cdylib backends, the Qt type-name mapping).
- Delete the embedded lidl_lexer/parser/serializer/validator/ast.
- Add experimental/lidl_compat.h: brings logos-lidl's std AST into the global
scope the backends use (via `using`), a qs() std::string→QString helper, a
QTextStream<<std::string overload, and name-compatible shims (lidlParse/
lidlSerialize/lidlValidate) so the emission code keeps compiling.
- Re-point impl_header_parser, lidl_gen_client (+ Doxygen /// docs on the
generated client methods), lidl_gen_cdylib, lidl_emit_common, and legacy/
main at lidl::ModuleDecl.
- CMake: C++17 + find_package(logos-lidl) + link logos-lidl::logos_lidl.
- bin.nix: distribute only the shared C++/Qt backend helpers (compat +
impl_header_parser + emit_common) under share/lidl-frontend, not the frontend.
- tests: drop the 4 frontend test files (covered by logos-lidl now); the
backend tests link logos-lidl.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: pin logos-lidl to the C-ABI commit + lock it
The logos-lidl input was declared in flake.nix but missing from flake.lock,
so override chains that don't reach the nested input (the doctest harness
building a scaffolded module) couldn't resolve it. Pin the branch rev and
lock it so the component is self-contained. Re-point at master once logos-lidl
lands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: re-point logos-lidl to merged master (#5)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Extract the protocol layer into logos-protocol; consume it as a flake input
The transport/token/IPC layer (transports incl. QRO + plain TCP/TLS,
consumer core LogosAPIClient/LogosAPIConsumer with the capability
auto-requestModule flow, ModuleProxy, token manager, QVariant<->JSON
conversion, the abstract LogosProviderObject interface) now lives in the
logos-protocol repo behind the versioned lp_* C ABI.
This SDK keeps the typed C++ developer layer (LogosAPI, provider base
classes + Qt provider glue, module context, code generator) and still
compiles the protocol sources INTO liblogos_sdk.a from the flake input,
so the installed artifact (archive symbols, include/ + include/cpp
layouts, cmake config) stays byte-compatible: existing consumers need
no changes. Public headers are unchanged; logos_provider_object.h keeps
its name and now re-exports the abstract interface from
logos_provider_interface.h.
Transport/protocol component tests moved to logos-protocol with the
code; the remaining sdk/generator/experimental suites are unchanged
(432/432 green against the local protocol checkout).
* lock: add logos-protocol input
* Make the base SDK Qt-free: move the Qt developer layer to logos-qt-sdk
LogosAPI, LogosAPIProvider, LogosProviderBase/LOGOS_PROVIDER macros, the
QObject provider glue (QtProviderObject) and the legacy PluginInterface
(core/interface.h) move to the new logos-qt-sdk repo. The protocol
sources are no longer compiled into a monolithic archive — consumers
link logos-qt-sdk (which layers on logos-protocol) instead.
What remains here is header-only std C++: logos_module_context.h,
logos_result.h (StdLogosResult), logos_json.h — exported as the CMake
INTERFACE target logos-cpp-sdk::logos_headers — plus the code generator
(a build-time tool; its introspection mode now includes
logos_provider_interface.h from logos-protocol, where
LogosProviderPlugin moved).
Mechanically verified Qt-free: the logos-cpp-lib / logos-cpp-include
closures contain only nlohmann_json. Tests: 245/245 (module-context std
suite + generator + experimental).
* fix: accept the installed source-export layout in the protocol-root check
The fail-fast only tested <root>/cpp/logos_protocol.h, but the LP_SRC
selection right below (and the error message itself) support the
installed export layout <root>/include/cpp as well. Pointing
LOGOS_PROTOCOL_ROOT at an installed export tripped the FATAL_ERROR
before that fallback could apply.
Caught by Copilot review on #82.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* lock: pin logos-protocol to the qt-free-split branch head
The Qt-free SDK (and the cdylib backend stacked on it) reference
LogosProviderPlugin from protocol's logos_provider_interface.h, which
lands on feat/qt-free-split — the P1-branch pin no longer compiles
standalone. Temporary — drop when the chain PRs merge.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* doctest: pin the logoscore runtime via its {release} placeholder
The spec built logoscore-cli at bare master with only the cpp-sdk inputs
overridden — master's stack cannot compile against the qt-free SDK, so
the suite failed on the chain branches. With the placeholder, CI's
--release-for pins expand it to the workspace's logoscore commit (and
local runs without a pin still fall back to master, unchanged).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* doctest: override the nested module builders to {release} too
capability_module (via logoscore's lock) and the cloned accounts module
resolve module-builder from their own locks — pre-split revs whose
LogosModule.cmake still detects the SDK by logos_api.h, which the
qt-free SDK no longer ships ('logos-cpp-sdk not found'). Overriding the
builder itself to the workspace-pinned chain rev (keeping the nested
cpp-sdk override) builds both modules with the split-aware builder.
Verified end-to-end locally with the exact doctest command.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* doctest: apply the {release} + nested-builder overrides to all three specs
The runtime spec got the treatment in 210eea1; the composition and
worker-thread specs have the same logoscore/module build commands and
failed identically (pre-split builders from the modules' own locks).
All executed run: blocks now pin logoscore-cli{release} and override
the nested module builders to logos-module-builder{release}; the
displayed code_block: variants stay in their generic master form.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* codegen: typed wrappers throw on call failure; dispatch catches escapes
Generated sync client wrappers call the new err-out invokeRemoteMethod
overload and throw logos::LogosCallError when the call fails (e.g. the
bound module is missing) — previously the empty QVariant silently
degraded to the return type's default and a caller could not tell
failure from a legitimate 0 / "". Both generators (legacy + LIDL),
both API styles. Async paths unchanged.
Generated provider dispatch (universal qt glue + LOGOS_PROVIDER) wraps
the method body in a catch-all that logs and returns an invalid QVariant
— an escaped exception becomes an ordinary METHOD_FAILED instead of
unwinding through Qt event dispatch and killing the module process.
* codegen: CallError out-param instead of throwing wrappers
Per review, the generated sync wrappers expose the error channel as an
optional trailing parameter — add(a, b, &err) — rather than throwing:
explicit, stateless, works on temporaries, and existing call sites
compile unchanged (they keep default-on-failure, now with a qWarning so
failures are visible in the module log). The dispatch catch-all from the
previous commit stays: it contains author exceptions, it doesn't
introduce any.
* glue: fire onContextReady AFTER modules()/event wiring
The generated onInit set the context (which fires the impl's
onContextReady hook) before constructing the LogosModules aggregate and
wiring typed event emission — so an impl doing its documented one-time
setup there (typed dependency calls, event subscriptions) dereferenced
a null aggregate and crashed the module process (signal 11). Found by
the first module to subscribe to a dependency's typed event from
onContextReady. Context now goes last.
* ci: run workflows on stacked PRs + workflow_dispatch
Both workflows filtered pull_request to master-based PRs, so stacked PRs
(feat/qt-free-sdk -> feat/extract-logos-protocol, feat/cdylib-authoring
-> feat/qt-free-sdk) ran NO checks at all. Drop the base-branch filter
for pull_request and add workflow_dispatch for manual runs. Same fix as
logos-module-builder 232b8a2.
* lock: protocol at the typed-requestModule port (3de5398)
* ci: chain pins for the doc-tests (drop at merge)
In repo CI only cpp-sdk's {release} is the commit under test —
logoscore-cli and module-builder expanded to master, which doesn't link
against the chain SDK the specs override in ('Build the CLI with the SDK
override' failed on every run since the stacked-PR triggers were
enabled). Pin both to the extraction-chain heads; the workspace pipeline
is unaffected (it pins every repo itself).
* generator: distribute the LIDL frontend for external generators
First step of moving ALL Qt glue emission out of this repo into
logos-qt-sdk's logos-qt-generator (cpp-sdk's generator keeps only the
Qt-free outputs: std typed wrappers, logos_sdk umbrella, cdylib
impl-exports, LIDL derivation).
- Shared emit helpers (lidlToPascalCase, lidlTypeToQt, lidlTypeToStd,
lidlIsStdConvertible) move to a new lidl_emit_common.{h,cpp} unit, used
by both generators.
- The frontend set (AST, lexer, parser, serializer, validator,
impl-header parser, emit-common) is installed under
share/lidl-frontend/ — the qt generator compiles these sources in
directly, so the two tools share one frontend without a binary ABI.
* lock: protocol#3 merged — pin advances to protocol master
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Extract the protocol layer into logos-protocol; consume it as a flake input
The transport/token/IPC layer (transports incl. QRO + plain TCP/TLS,
consumer core LogosAPIClient/LogosAPIConsumer with the capability
auto-requestModule flow, ModuleProxy, token manager, QVariant<->JSON
conversion, the abstract LogosProviderObject interface) now lives in the
logos-protocol repo behind the versioned lp_* C ABI.
This SDK keeps the typed C++ developer layer (LogosAPI, provider base
classes + Qt provider glue, module context, code generator) and still
compiles the protocol sources INTO liblogos_sdk.a from the flake input,
so the installed artifact (archive symbols, include/ + include/cpp
layouts, cmake config) stays byte-compatible: existing consumers need
no changes. Public headers are unchanged; logos_provider_object.h keeps
its name and now re-exports the abstract interface from
logos_provider_interface.h.
Transport/protocol component tests moved to logos-protocol with the
code; the remaining sdk/generator/experimental suites are unchanged
(432/432 green against the local protocol checkout).
* lock: add logos-protocol input
* fix: accept the installed source-export layout in the protocol-root check
The fail-fast only tested <root>/cpp/logos_protocol.h, but the LP_SRC
selection right below (and the error message itself) support the
installed export layout <root>/include/cpp as well. Pointing
LOGOS_PROTOCOL_ROOT at an installed export tripped the FATAL_ERROR
before that fallback could apply.
Caught by Copilot review on #82.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* lock: protocol at the typed-requestModule P1 port (1e4bc72)
* lock: protocol at master (protocol#2 merged)
The extraction is on protocol master now (29afbac); the temporary branch
pin is dropped.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>