mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 09:41:06 +00:00
docs-drop-archived-module-client-refs
24
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
95d7b3a9c5 |
feat: split the SDK by capability, retire the provider-header path, and harden the cdylib decode (#138)
* feat(generator): remove --provider-header (interface: "provider")
Every provider now goes through the module-impl C ABI, so the LOGOS_METHOD
dispatch path is gone: parseProviderHeader, generateProviderDispatch,
ParsedMethod and the joinDocLines helper only it used (~300 lines), plus the
test that covered it.
`toQVariantConversion` is NOT removed — it is shared with live emitters — and
its test stays.
The flag is REFUSED rather than dropped. Without that, `--provider-header x.h`
falls through to the plugin-path branch, which reads the flag itself as a
plugin path and reports "Plugin file does not exist: --provider-header" — a
missing-file error for what is really a retired mode. It now exits 2 with a
message pointing at interface: "universal".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(sdk): logos_host_services.h — the C++ veneer over the privileged surface
Phase C1. A Qt-free, header-only wrapper over the three trust-root lp_* calls
A3 added, so capability_module can become an ordinary universal module instead
of a hand-written Qt plugin reaching into TokenManager directly.
Deliberately FREE FUNCTIONS, not a LogosModuleContext seam as the plan
sketched. The grant is process-global per IMAGE (host binary and module cdylib
each link their own logos-protocol, so each has its own grant state and its own
TokenManager), so the gate lives in the caller's own image and there is nothing
per-instance to inject; a context seam would imply the privilege is a property
of one impl object, which it is not. It is also markedly cheaper: a seam would
need a new module-impl C ABI export plus lockstep changes in BOTH codegen paths
(the Qt provider glue and the cdylib wrapper).
constantTimeEquals lives here rather than in each caller: the natural spelling
(a == b) leaks the matching-prefix length through timing, and a trust root
comparing tokens with == is the exact bug this file exists to prevent. Ported
from capability_module's own implementation to std::string.
Two things the tests caught that reading had not:
* lp_inform_module_token_to takes SIX arguments (client, auth_token,
origin_module, module_name, token, timeout_ms), not the three I first wrote.
The wrapper now mirrors it exactly, with the protocol's own default-timeout
semantics documented.
* sdk_tests compiles against logos_headers alone, which carries no protocol
include path. It now resolves logos_protocol.h from LOGOS_PROTOCOL_ROOT,
accepting either the source layout (cpp/) or a package layout (include/) and
failing loudly on neither, rather than hard-coding the one in use today.
The suite deliberately does NOT link logos-protocol: the lp_*-calling wrappers
are `inline` and never ODR-used by these tests, so no protocol symbol is
referenced. That is itself the assertion — the veneer must not drag the
protocol library into a header-only consumer. A future test that calls one will
fail to LINK rather than silently pull it in.
Also documents a real gap found while writing it: lp_token_get performs NO
host-service check, so "token_registry" gates ENUMERATION only. The plan claims
that service covers `lp_token_get(any)`; it does not. Flagged at the call site
rather than papered over — if lookup should be gated, the gate belongs in
lp_token_get.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(generator): emit logos_module_grant_host_services in the cdylib exports
Completes the C1 codegen half. A3 declared this entry point in
logos_module_impl.h and documented that the grant MUST cross the C ABI, but the
generator never emitted it, so nothing could open a module's gates.
The body forwards to lp_grant_host_services in the MODULE's own image, which is
the entire point. Verified that premise on a real built module rather than
taking it from the header comment: test_basic_module_cpp_plugin.dylib DEFINES
25 lp_* symbols and imports zero — logos-protocol is statically linked into
each plugin, so the module's gate state really is its own, and a grant recorded
only in the host would leave lp_token_keys() returning null forever. That
failure is silent: null is indistinguishable from an empty token store.
Emitted unconditionally rather than behind a codegen flag. Which modules are
privileged is the host's decision — it pushes nothing to an ordinary module —
and lp_grant_host_services validates the names and fails closed, so a per-module
flag would only add a second place for declaration and capability to disagree.
The comment states the boundary honestly: this is a declaration-and-audit
mechanism, NOT a defence against a hostile module. The cdylib links
logos-protocol, so its own code can call lp_grant_host_services() directly and
self-grant. What the gate buys is that the privilege is explicit, greppable and
off by default. Isolation between modules rests on process separation, the auth
token, and the target's allowedCallers.
Three tests, one per property that could regress independently: the export
exists; its body actually forwards (a stub returning 0 would make every host
push look successful while both gates stayed shut); and it is emitted for an
ordinary module too, not only for privileged ones.
Confirmed in the built artifact: nm on a real universal module lists
_logos_module_grant_host_services alongside the other seven module-impl exports.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(generator): guard the grant export on the protocol MINOR that added it
The emitted logos_module_grant_host_services calls lp_grant_host_services,
which logos-protocol only gained at MINOR 3. A module built against an older
protocol therefore failed to compile in GENERATED code its author never wrote.
Found by giving logos-template-module a standard flake: its own lock resolves
protocol master, and the build died on `use of undeclared identifier`.
Guarded on LOGOS_PROTOCOL_VERSION_MINOR >= 3. A module built against 0.2 has no
grant entry point at all, which is the same fail-closed state as never being
granted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: point logos_async_result.h at the one LogosModule.cmake
The comment named "logos-plugin-qt/cmake/LogosModule.cmake and its
module-builder twin". There is no twin any more: logos-plugin-qt's copy is
deleted and the file exists once, in logos-module-builder.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(deps): rev-pin logos-protocol at c8bab12 (the trust-root surface)
logos_host_services.h is a veneer over lp_token_keys /
lp_inform_module_token_to / lp_grant_host_services, which landed on
logos-protocol's feat/per-client-token-store branch and are NOT on its
master — master is still LOGOS_PROTOCOL_VERSION_MINOR 2, so the `tests`
check could not compile against the previously locked 03842db.
Rev-pinned in the URL rather than left master-tracking, because
`nix flake update` cannot reach a commit that is not on the tracked
branch. Re-point at master once that branch merges.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(generator): remove --module-dir, and the two dead files it documented
--module-dir walked a directory of BUILT plugins and generated one consumer
wrapper per dependency by dlopen'ing each and reading its QMetaObject. Every
wrapper now comes from a contract instead -- `--general-only` with one
`--dep <name>=<name>.lidl` per dependency -- which builds no dependency plugin
and, unlike introspection, works under cross-compilation.
It is REFUSED rather than ignored, mirroring --provider-header right below it.
Falling through to the dependency LISTING would have exited 0 having generated
nothing: the exact shape that lets a stale caller look green while shipping a
module with no typed API.
No nix build changes as a result -- the flag had no caller left in any of them,
so a store-path diff would be empty either way and would prove nothing. The
only observable difference is what the binary does when handed the flag, so
that is what the new `generator-cli` check asserts, by EXIT CODE:
OK: control - --metadata alone exits 0 and lists dependencies
OK: --module-dir exits non-zero (status=2)
OK: --module-dir fails with the removal diagnostic
OK: --general-only still emits the umbrella
The control matters: without it a non-zero exit could equally mean the binary
is broken. It runs against an EXISTING modules directory too, because the old
code only errored when that directory was missing.
Also deleted, both genuinely dead:
* cpp/compile.sh -- compiles logos_api.cpp, module_proxy.cpp, token_manager.cpp
and six headers, NONE of which exist in this repo any more (they moved to
logos-protocol / logos-qt-host in the host split). The script cannot run.
* docs/docs.md -- 789 lines with zero inbound references anywhere in the
workspace, documenting --module-dir and a cpp/ layout that is gone.
cpp-generator/compile.sh is KEPT: logos-module-builder's LogosModule.cmake:404
still invokes it (`add_custom_target(cpp_generator_build ...)`) on the
LOGOS_CPP_SDK_IS_SOURCE branch, and it builds into exactly the
LOGOS_DEPS_ROOT/build/cpp-generator path that file then reads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(generator): the umbrella emitter leaves legacy/, and gets its own mode
`cpp-generator/legacy/` held four things and only one was legacy. The shared
emitter library was misfiled there: `generator_lib.{h,cpp}` is already consumed
by the MODERN `experimental/lidl_gen_client.h` and by all 11 tests under
tests/generator/. `lidl_to_json.{h,cpp}` likewise. Both are now
`cpp-generator/`; `legacy/` is down to `main.cpp` + `legacy_main.h`.
The logos_sdk umbrella (`struct LogosModules`) is not legacy either — it is the
CURRENT typed-dependency surface. `LogosModuleContext::modules()` returns it,
so every universal module that calls a declared dependency goes through it, and
LogosModule.cmake runs `--general-only` for every module build. Yet the only
code that could emit it lived inside the directory the plan wants deleted.
So `cpp-generator/main.cpp` gains `--umbrella`, with `--general-only` routed to
the same implementation and dispatched before the fall-through to legacy_main.
The deps-driven emission needed no rewriting: `makeUmbrella{Header,Source}
FromDeps` were already in generator_lib, and legacy/main.cpp merely wrapped
them in file I/O. -352 lines from legacy/main.cpp (827 -> 475), including the
interface-wrapper helpers that only that branch used.
`--general-only` keeps working identically, because LogosModule.cmake and
logos-basecamp both call it. The alias is guarded on `--metadata`, since
`--general-only` was never a standalone mode — without metadata it fell through
and reported the flag as a missing plugin path, and it still does.
The scraping `writeUmbrellaHeader`/`writeUmbrellaSource` are untouched: they
belong to `generateFromPlugin`, the QPluginLoader introspection path, and die
with it.
Verified byte-identical, which is the whole claim of a relocation. An
adversarial pass built its own pre- and post-change binaries and diffed the
emitted `logos_sdk.{h,cpp}` across 12 real metadata.json files x {qt,lp} x
{--general-only,--umbrella}: 48/48 identical, stdout/stderr/exit included, with
a positive control (qt vs lp) confirming the harness can see a difference. Real
modules then built through logos-module-builder against both binaries with
`diff -r` empty, including the compiled plugin. 266/266 tests pass, and the
pre-change tree also reports 266, so no test was silently dropped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(sdk): split the SDK by capability, and add the host façade
Host, module-consumer and module-provider are three distinct capabilities. The
SDK exposed them as one target, so a program linked all three regardless of
what it was. Each now gets its own INTERFACE target:
::common logos_json.h, logos_result.h
::consumer logos_lp_client.h, logos_async_result.h
::provider logos_module_context.h, logos_host_services.h
::host logos_host_core.h (new)
`logos_headers` stays as an umbrella over all four, so the ~70 existing
consumers are unaffected — logos_module_context.h alone has 66. Migrate to the
narrow targets when touching a repo; additive first, removal second.
NOTE the misnomer the split exposes: `logos_host_services.h` is MODULE-side
despite its name — it is the veneer a privileged module uses for services the
host granted it — so it belongs to ::provider, not ::host. Renaming it touches
9 files across 5 repos, so it is left for a change that can carry that cascade.
── logos_host_core.h ───────────────────────────────────────────────────────
`logos::host::LogosCore`, a plain RAII wrapper over liblogos' logos_core_* C
API, for the four programs that stand up a core (basecamp, logoscore-cli,
standalone-app, module-viewer). They currently open-code the same calls, and
basecamp had already grown a private wrapper for them.
It is deliberately an ORDINARY class — no codegen, no injection seam, no
void*. LogosModuleContext needs `_logosCoreSetContext_`, SFINAE `maybeSet*`
helpers and a void* round-trip because a module impl is user-authored but
FRAMEWORK-instantiated. A host is main(): it constructs this itself. For the
same reason there is no `modules()` here — the host holds its own LogosModules
from its own generated logos_sdk.h, so this header needs no generated type.
What it earns, each tied to a measured hazard:
* OWNERSHIP. liblogos allocates its char**/char* returns with new[], so
`delete[]` is correct and free() is undefined behaviour. That rule lived in
a comment in one repo's .cpp; it is now in one place.
* ORDERING. Three setters must precede logos_core_start(), stated only in
comments in logos_core.h. They are constructor arguments here, so the
illegal order is not expressible.
* SHAPE. logos_core_get_module_stats() takes no module name and returns one
blob for every module; stats(name) does that parse once.
The logos_core_* ABI is re-declared rather than included: logos-liblogos
depends on logos-cpp-sdk, so including its header would invert the graph. Every
host already hand-declares it; this makes it one declaration instead of four.
15 tests, 281/281 suite total. They define the extern "C" ABI themselves and
allocate exactly as liblogos does, so the ownership rules are exercised rather
than asserted; the ordering test records call order and pins start() as last.
Also fixes a real gap: nix/include.nix carries its own header list, separate
from cpp/CMakeLists.txt's install(FILES), so a new header silently did not ship
in the export layout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(generator): a Qt-typed umbrella that needs no LogosAPI
Splits a consumer's TYPE SURFACE from its TRANSPORT. Until now the qt umbrella
was `explicit LogosModules(LogosAPI* api)` while the lp one was default-
constructible, so "Qt types" implicitly meant "has a LogosAPI" — and a cdylib
module, whose provider surface is the std logos_module_impl.h C ABI and which
holds no LogosAPI anywhere, could not have Qt-typed dependency wrappers at all.
Its generated glue emits `new LogosModules()` unconditionally
(lidl_gen_cdylib.cpp:693), so the combination did not merely misbehave, it did
not compile.
That was a codegen choice, not a law: the wrapper bodies already run over lp_*.
`--binding api|origin` selects it, defaulting to `api`. A second enum rather
than a third ApiStyle value, deliberately: ApiStyle names the type surface and
is switched on by six emitters (makeHeader/makeSource/returnTypeFor/
paramTypeFor/toWireFor/fromWireFor); a "Qt types, explicit origin" member would
force all six to answer a transport question whose honest answer is "same as
Qt" every time. ApiStyle::Lp ignores the new axis — lp is origin-bound by
construction — and that is asserted rather than assumed.
The emitted umbrella bakes metadata.json#name as the origin literal:
LogosModules() : test_fullapi_cpp(QStringLiteral("test_fullapi_qtproxy")) {}
FullApi bind_full_api(const QString& moduleName) {
return FullApi(QStringLiteral("test_fullapi_qtproxy"), moduleName); }
Origin is the CONSUMER's own name and target is the dep — origin first in both
bind_ overloads. This is the load-bearing property: LpBridge::forTarget derives
origin from `api->moduleName()`, and reusing it silently gives a consumer the
caller's identity, which has already preserved a privilege escalation once in
this tree. An empty metadata name is refused at the CLI (exit 6, naming the
file) and emits `#error` in the header: a module that cannot state its identity
must not compile, and must never be handed a blank or borrowed one.
Verified additive on 172 real metadata.json x 2 api-styles = 344 runs, all
producing output, byte-identical old binary vs new. Mutation control: swapping
bind_<iface>'s (origin, moduleName) to (moduleName, origin) fails the suite at
MakeUmbrellaTest.QtExplicitOriginStatesTheConsumersOwnNameEverywhere. 281 -> 286
tests.
Framing worth keeping: the origin is SELF-ASSERTED from the module's own
metadata and is not attested by the transport. That is not a regression —
`api->moduleName()` is equally process-stated — but "explicit origin" means the
module names itself, not that the host vouches for the name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(generator): stop pointing callers at a flag that no longer exists
--backend qt is deleted from logos-qt-generator, and this repo was still
signposting it. main.cpp's refusal said "Use it for --backend qt", and the usage
text advertised --lidl … --backend qt and --from-header … --backend qt. Those
now point at nothing — the exact failure the deletion removes, one repo over.
The refusal names the real replacement chain instead: --backend cdylib here,
then logos-qt-host-generator --backend cdylib for Qt-plugin packaging.
docs/project.md's "Provider Generation" section documented three emitters that
no longer exist; rewritten to state the seam and the two-step pipeline.
docs/spec.md's dataflow diagram showed <name>_qt_glue.h / <name>_dispatch.cpp as
outputs; the diagram is corrected and the sections describing that shape are
marked historical rather than deleted, because the onInit wiring they document
still applies to the cdylib glue.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(cpp-generator): merge legacy/ into the generator, dropping its dead half
`legacy/` was never a library with an API surface. Two files, 475 lines, exactly
one exported symbol — `int legacy_main(int, char**)` — with every other
definition `static`, compiled INTO logos-cpp-generator and reached by fallthrough
at the end of main(). So there was nothing to keep separate: it is one mode of
this binary, and it now lives beside the others as plugin_introspect.{cpp,h}
behind `runPluginIntrospectMode()`, named for what it does.
Deleting it was never an option — that premise was checked and refused earlier.
`--general-only` alone has ~10 live callers across 7 repos including the central
module path (buildPlugin.nix:206,211, buildHeaders.nix:221,
LogosModule.cmake:423). The mode is load-bearing; only its packaging was wrong.
110 lines go with the move, all genuinely unreferenced:
* cppStringEscape — zero callers anywhere.
* writeUmbrellaHeader / writeUmbrellaSource and the `if (!moduleOnly)` block
that called them. This is the real prize: a SECOND, directory-SCRAPING
implementation of logos_sdk.{h,cpp}, unreachable in practice because
generate-module-headers.sh:60 always passes --module-only. generator_lib's
deps-driven makeUmbrella*FromDeps is now the only umbrella emitter, so the
two cannot drift.
* a dead `QJsonDocument doc(methods);` and its commented-out use.
With the block gone, `--module-only` suppresses nothing, so the parameter and
its plumbing go too. The FLAG stays tolerated rather than rejected, because
generate-module-headers.sh passes it unconditionally — a comment at the old
parse site says so.
Verified: #default builds, and both checks pass — `generator-cli` (which
exercises the CLI surface, including --general-only) and `tests`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* feat(sdk): make the by-name call path a supported API
The dynamic (by-name) invoke path already existed and was already ungated at
every layer — lp_client_create / lp_invoke in the C ABI, logos::LpClient above
it, and the Qt client above that. Nothing checked a host service; there was no
gate to open. What was missing was the ERGONOMICS, which is what turned a
supported capability into something callers reached around the umbrella to get.
Three additive pieces, no gate touched:
1. LogosModuleContext::moduleName() — the module's own registry name, i.e. the
origin it authenticates as. The typed wrappers bake their origin in at
codegen time; a by-name call has to state one, and a wrong origin
authenticates as nobody and fails far from the call site. Set through a NEW
`_logosCoreSetModuleName_`, deliberately not a fourth parameter on
`_logosCoreSetContext_`: every generated provider calls that signature, so
widening it would break each one until regenerated, for a value the
generator knows statically. Set before the context, so moduleName() is live
inside onContextReady().
2. LogosModules::dynamic(target) on the origin-bound umbrella — the untyped
client, with the origin baked in exactly as the typed members' is, and
cached per target because LpClient owns a connection. The typed members over
metadata.json#dependencies stay the ordinary way to call another module;
this is for the cases whose target is a runtime value (a proxy, a router).
3. LpClient::getMethods() over the already-exported lp_get_methods. Invoke
without introspect is guessing — a caller that cannot ask what exists can
only hardcode, and a wrong guess fails at runtime like a typo.
Verified: #default builds, and both checks pass (tests, generator-cli).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cdylib): shape-check [any]/{tstr:any} args, and fix the umbrella's includes
TWO fixes; the second is a regression I introduced two commits ago.
1. jsonArgToStd() returned the raw json for LogosList / LogosMap — "untyped JSON
passes through, as it always has". Arity was checked, type was not, so a
scalar reached a `[any]` parameter untouched. A proxy forwarding it through a
Qt-typed consumer then turned "notalist" into
["n","o","t","a","l","i","s","t"], because qvariant_cast reads a QString as a
sequential container — and the downstream provider saw a well-formed array
with nothing left to refuse. Now emits logos::jsonRequireArray /
jsonRequireObject; the throw lands in the dispatch's existing catch as
{"code":"dispatch_failed"}. Bare `any` stays raw, deliberately: it declares
nothing, so there is nothing to check it against.
Measured on the conformance matrix: closes all 8 failing cells (498/22/12/8
-> 500/18/12/2), and a whole-matrix per-cell diff shows exactly 14 status
changes, every one inside the two hostile cases. The other 518 cells are
byte-identical in status and value. The remaining 2 are the fix working — the
C++ cdylib provider now refuses the same input, which cases.json still pins
as lenient via expect_by_provider; that is a registry edit, and known.json's
Q1b already names this exact outcome as the intended fix.
2. The Lp umbrella emitted `logos::LpClient& dynamic(...)` and a
std::map<..., std::unique_ptr<logos::LpClient>> while <map>, <memory> and
logos_lp_client.h were conditional on interfaceNames. A module WITH
dependencies compiled by accident, because <dep>_api.h drags the header in
transitively. A module with NO dependencies and no interfaces includes
nothing else and failed outright with "no type named 'LpClient' in namespace
'logos'". test_fullapi_cpp is exactly that shape.
I shipped that in
|
||
|
|
e3744fb84c |
feat(windows): add an x86_64-windows cross target (#136)
* feat(windows): add an x86_64-windows cross target - packages now go through forAllTargets, adding the "x86_64-windows" pseudo-system alongside the native ones. - qt6.wrapQtAppsNoGuiHook is dropped for Windows and dontWrapQtApps set on each derivation. Both halves are needed: the hook does not evaluate for a mingw host, it would skip a PE anyway (wrap-qt-apps-hook.sh only handles ELF and Mach-O), and qtbase's setup hook hard-errors in qtPreHook without the flag. - cmakeFlags pick up pkgs.logosQtCrossCmakeFlags, which is empty natively. - meta.platforms widened to include windows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(windows): fail loudly when the generator binary is missing The install test named only the unsuffixed `logos-cpp-generator` and had no else-branch, so a mingw build — which produces `logos-cpp-generator.exe` — copied nothing, succeeded, and shipped an EMPTY $out/bin. The failure then surfaced in whichever consumer tried to run the generator, nowhere near the cause. Probe both names, and exit 1 with a directory listing if neither is there. Verified: the Windows build now installs a real PE, and the native build is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(deps): re-pin logos-nix, logos-protocol and logos-lidl to their merged revs L1 (logos-nix) and L2 (logos-protocol, logos-lidl) are on their default branches now, so the lock can name the merged revs instead of the pre-merge branch tips it was resolving against while those PRs were open. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
9d508292eb |
fix(generator): defer generated event subscriptions instead of acquiring a replica (#134)
* fix(generator): defer generated event subscriptions instead of acquiring a replica
Both C++ generators emitted, at all three subscription sites (the generic
on(QString, RawEventCallback), its EventCallback overload, and every typed
on<Event>):
LogosObject* origin = ensureReplica(); // blocking requestObject
if (!origin) return false; // PERMANENT -- never retried
m_client->onEvent(origin, eventName, callback);
That asks "is the module reachable right now" at the one moment the answer is
no. Every C++ consumer subscribes from init(), onContextReady() or a backend
constructor, all of which run while the dependency's host has been spawned but
has not called listen() yet. The guard inside requestObject was dead code for
years -- isConnected() returned a latch that was always true -- so the call fell
through to a blocking wait that usually succeeded, slowly. Making isConnected()
truthful turns the same code into an instant, permanent, silent failure: the
wrapper compiles, returns a bool, and never delivers.
All three sites now route through the deferred channel:
return m_client->onEventWhenAvailable(m_moduleName, eventName, callback) != 0;
and ensureReplica() / m_eventReplica are deleted from both generators. Keeping a
per-wrapper replica would reintroduce both halves at once -- a blocking acquire
on the subscriber's thread, and a permanent failure when the module had simply
not started yet.
The return becomes ACCEPTED rather than live, false only for errors no retry can
fix. That is stated in the emitted comment so it reaches every generated file
rather than only this message.
VERIFIED AT THREE LEVELS, because the first two prove less than they look:
emits -- 265/265 cpp-sdk tests. The goldens now pin the emitted CALL SITE and
EXPECT_FALSE the removed symbols; they are string comparisons and
would pass on code that does not compile, which is exactly how this
defect survived.
compiles-- both generators' output compiled against the local protocol branch
(EXIT=0), including a 15-event contract with a 3-parameter event.
defers -- real A/B on a live qt_remote transport with real generated code:
7/7 green on the migrated generator, 3/3 red in 0-8 ms on the
pristine one, with published-first controls green in both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(deps): bump logos-protocol to 0183e8c for onEventWhenAvailable
The generator change in this PR emits `m_client->onEventWhenAvailable(...)` at
all three subscription sites. This repo pinned logos-protocol 0f26ffd, which has
zero occurrences of that symbol -- compiling the emitted wrapper against it gave
EXIT=1 and 16 errors, every one "no member named 'onEventWhenAvailable' in
'LogosAPIClient'". That is why this PR was opened as a draft and why the bump has
to ride in the SAME commit range as the emission change: split them and cpp-sdk
master is red for every Qt-api-style consumer.
0183e8c is logos-protocol master with #47, #53 and #55 in. It is deliberately not
the first commit that introduces onEventWhenAvailable: #47's tip also carries the
use-after-free fix for tryAcquireNow (09f684f), without which a consumer that
subscribes more than once to a not-yet-reachable module frees a QtRO facade that
is still registered in a shared replica implementation's connect list. Generated
Qt consumers subscribe exactly that way -- one on<Event> per declared event, from
init() -- so pinning below that commit would make this change crash rather than
merely fail to compile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
dfd46282a7 |
chore: pin logos-protocol 0f26ffd, and move CI to Nix 2.35.1 (#133)
Bumps this repo's own `logos-protocol` root pin from 3a31c91d (Jul 31) to 0f26ffd (Aug 6, current master). Span is two protocol PRs, #40 and #41. Why per-repo pins rather than `follows` overrides: on macOS, two different protocol revisions statically linked into one process get their weak definitions coalesced by dyld, so one image's protocol code silently binds to the other's. That is not a build error — it is a runtime miscompile, and it bites across real ABI changes (`awaitCompletion` gained parameters; `RpcValue`'s variant gained `uint64_t` in the middle, renumbering later alternatives). The durable fix is for every repo in the chain to pin the same protocol, so no override is needed anywhere. CI: cachix/install-nix-action v27 -> v31, i.e. Nix 2.22.1 -> 2.35.1. 2.22.1 is old enough to mis-evaluate flake input overrides newer Nix handles fine. Nothing in this commit needs 2.35.1 — verified to evaluate on 2.22.1 too — but the rest of the chain does, and logos-standalone-app already runs v31 for exactly this reason. Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
8c3d7d4253 |
chore(deps): bump logos-protocol to 3a31c91d (off-strand socket close races) (#128)
Picks up logos-protocol #38 and #39, two off-strand socket-close races in the plain (tcp / tcp_ssl) transports: #38 RpcConnection::fail() closed the socket on the caller's thread while every other stream access was serialized on m_strand, racing kqueue_reactor::start_op and segfaulting the client on teardown. #39 The same class of bug on RpcServer's acceptor. Both are FALSE NEGATIVES rather than lost work: the RPC completes and prints its correct result, then teardown crashes, so a caller that checks the exit code before parsing stdout reports a healthy call as failed. Measured at ~0.45% of calls over tcp/tcp_ssl, zero over local/QtRO. logos-cpp-sdk ships no protocol runtime code of its own -- the generator only #includes protocol headers (logos_provider_interface.h, logos_json_convert.h) and the header-only SDK does not link it at all. So this is a propagation bump: it is cpp-sdk's pin that downstream repos inherit through `follows`. Verification: * The lock has exactly one logos-protocol node and it is on 3a31c91d; no other node in the closure carries a protocol input. * The real (unwrapped) generator binary is BYTE-IDENTICAL across the bump -- sha256 1ceb6b9d2517d58f...; only the Nix wrapper differs, and only in its own embedded store path. Explained by the upstream diff, which touches solely cpp/implementations/plain/rpc_{connection,server}.* plus protocol's own tests. * Emitted-code equivalence over the workspace corpus, base vs bumped generator: 142 invocation pairs / 469 emitted files, zero differences, across --general-only (--interface and --dep, both --api-style qt and lp), --lidl, --lidl --backend cdylib --impl-class, --from-header --backend cdylib, and --header-to-lidl. Harness sensitivity proved by a qt-vs-lp negative control (42/42 differ) and by input mutation (39/39 successful runs change). * Unit tests: 218/218 pass, identical to origin/master (218/218). Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
198f0317ca |
feat(optional): ?T is two-state, and the generators finally read it (#125)
* feat(optional): ?T is two-state, and the generators finally read it
No generator in any language read the optional flag — it had never been
implemented. `?T` was a HARD REJECT on the cdylib backend ("module not
cdylib-eligible"), `std::optional<T>` in an impl header fell through to the
opaque `any` with no diagnostic, and a `? name: T` field was emitted as a
required `T`. Three real contracts in the workspace already declare optionals
and were silently getting one of those three answers.
`?T` is TWO-state: a value of T, or empty. Never three — "one LIDL type <-> one
type per language" leaves nowhere for a third state, because every target has
exactly one empty inhabitant.
ONE MEANING, TWO SPELLINGS. `? name: T` (the field flag) and `name: ?T` (the
type kind) are the same declaration. Backends no longer answer that themselves:
logos-lidl's fieldIsOptional/fieldValueType are re-exported from lidl_compat.h
and every site THIS COMMIT TOUCHES reads them, so the two spellings emit
byte-identical code on the cdylib and client backends. That
caught a live drift on the way in — lidlRecordCollidesWithBytesTag read `f.type`
and so refused `? _bytes: tstr` while letting `_bytes: ?tstr` straight through,
one declaration with two answers.
THE WIRE RULE DEPENDS ON THE SLOT. Absent and explicit null are the SAME state
on decode and DIFFERENT on encode:
- decode is liberal, by exactly one inhabitant: in an optional slot absent and
null both mean empty; in a required slot both stay errors. A present value
goes through the decoder a required T would get, so a wrong type still fails
at the same path — optional widens the domain, it does not switch checking
off. `?bstr` therefore keeps the LENIENT bytes decode a bare `bstr` gets,
rather than silently becoming stricter in the optional slot.
- encode has one canonical form: empty OMITS the key where the slot is NAMED
(a record field) and is spelled null where it is POSITIONAL (an argument, a
return, an event parameter — no key to omit, and arity must not change). Key
omission lives in the record emitter because a Codec only ever sees a value,
never the slot it sits in. A round trip therefore canonicalises.
- `?any` collapses onto `any`: nlohmann::json already carries null, so
std::optional<LogosMap> would give the slot two spellings of empty.
The dispatch gate now admits a missing trailing optional argument and
materialises it as null, exactly the way a missing record field already was. A
method with no optional parameter emits the byte-identical gate it always did.
Header-first: `std::optional<T>` <-> `?T`, composing with records and
containers. `std::optional<std::optional<T>>` has NO LIDL type (three C++ states
over a two-state wire), so it maps down to `?T` — which makes the author's own
declaration stop compiling against the generated codec, deliberately — and says
so at derivation time instead of leaving a conversion error in generated code.
The Qt/Lp consumer surface is NOT fixed and does not pretend to be. The wrappers
real modules get come from legacy/main.cpp, where the AST is flattened to a
single Qt type-name string per slot before optionality could be seen; Qt has no
optional metatype, so `?T` lands on QVariant — the right shape (an invalid
QVariant is Qt's empty inhabitant) with no type. The generator now prints a Note
naming every flattened slot so an affected build is never silent, and
docs/project.md records exactly what a Qt consumer will still do with an
optional field.
Verified by output equivalence, not by a green build: the generator was built
before and after and run over every .lidl in the workspace plus the impl-header
fixtures, in cdylib, consumer-qt, consumer-lp, client and header-first modes.
428 of 465 artefacts are byte-identical; all 37 that differ belong to one of the
four contracts that declare an optional (the 38th path is the manifest). The
harness's sensitivity is pinned by a negative control: qt vs lp output differs
in 45 files. The emitted codec was additionally compiled under -Wall -Wextra and
run against the rules above — omission, absent==null, required-still-rejects,
present-but-wrong-still-fails, and canonicalising round trip.
Tests: 199 pass, 0 fail (180 before, 19 new).
Requires logos-lidl's optionality accessors and logos-protocol's
Codec<std::optional<T>>.
NOT FIXED, AND IT IS THE PATH THAT MATTERS MOST. The legacy interface-wrapper
path is untouched, and it is the one every real module builds through
(buildPlugin.nix:145 -> logos-cpp-generator --general-only). There the two
spellings still diverge:
? maybe: tstr -> QString maybe{}; __m.value("maybe").toString()
maybe: ?tstr -> QVariant maybe{}; __m.value("maybe")
and --api-style lp diverges too, neither side being std::optional. So R3 holds
on the backends below and NOT on the Qt consumer a shipping module actually
gets. logos-chat-module -- the contract that prompted this work -- uses the
field-flag spelling, so it lands on the branch that silently defaults.
The cause is upstream of codegen: legacy/main.cpp's moduleRecordsToJson and
moduleMethodsToJson flatten every TypeExpr to a single Qt TYPE-NAME STRING, so
optionality (along with nesting, map key types and descriptions) is gone before
generator_lib.cpp sees it. Widening that interface is a larger change and is
deliberately not attempted here. The only R3 test on a Qt surface covers
lidl_gen_client.cpp, which is on no live build path.
* chore: re-pin logos-lidl to master for the optionality accessors
lidl_compat.h re-exports typeIsOptional / optionalValueType / fieldIsOptional /
fieldValueType / paramIsOptional / paramValueType, which landed in
logos-lidl#7. The pinned lidl predated it, so CI failed to compile.
logos-lidl 8c95d4f -> 35f33d8. Tests: 199 pass, 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: re-pin logos-protocol to master for Codec<std::optional<T>>
The generated record codecs emit Codec<std::optional<T>> for an optional
field; that specialisation landed in logos-protocol#37 and the pinned
protocol predated it.
Note this repo's own tests would NOT have caught the omission -- the
generator tests string-assert emitted text rather than compiling it, so a
missing codec specialisation only surfaces when a real module compiles
generated optional code (logos-test-modules' ext provider).
logos-protocol 4359557 -> 72754ab. Tests: 199 pass, 0 fail.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(doctests): override logos-lidl alongside every logos-cpp-sdk override
The doc-tests build downstream repos (logoscore-cli, capability_module,
accounts_module) with --override-input logos-cpp-sdk. Nix does not carry the
overridden input's OWN lock, so those builds got this branch's cpp-sdk source
while still resolving logos-lidl from their own, older locks. The shipped
share/lidl-frontend/lidl_compat.h then calls accessors that lidl does not
have:
lidl_compat.h:46: error: 'paramValueType' has not been declared in 'lidl'
lidl_compat.h:92: error: 'fieldValueType' was not declared in this scope
Every --override-input logos-cpp-sdk now has a matching
--override-input <same-path>/logos-cpp-sdk/logos-lidl.
This is specific to the override path. A normal consumer running
'nix flake update logos-cpp-sdk' inherits cpp-sdk's own lock, which pins the
lidl carrying these accessors, and is unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(doctests): move logos-lidl at the qt-sdk nodes, not under logos-cpp-sdk
The doc-tests failed to build logos-qt-generator:
share/lidl-frontend/lidl_compat.h:46: error: 'paramValueType' has not been
declared in 'lidl'
MECHANISM. This SDK installs cpp-generator/experimental/lidl_compat.h into
$out/share/lidl-frontend/, and logos-qt-sdk's logos-qt-generator *compiles*
that installed header against qt-sdk's OWN logos-lidl input. Under
logos-qt-sdk, logos-lidl is a SIBLING of logos-cpp-sdk, not a descendant:
logos-qt-sdk
|-- logos-cpp-sdk <- --override-input moves this to the commit under test
`-- logos-lidl <- stays on qt-sdk's lock (8c95d4f), lacks the accessors
logos-logoscore-cli and logos-module-builder both declare
`logos-qt-sdk.inputs.logos-cpp-sdk.follows = "logos-cpp-sdk"` but no lidl
follows, so overriding the SDK hands qt-sdk a new lidl_compat.h next to its
old lidl. The failing derivation is logos-qt-generator — not anything in
logos-cpp-sdk, which is why the previous attempt aimed at the wrong node.
THE FIX is one `<path-to-logos-qt-sdk>/logos-lidl` override per qt-sdk node
that ends up on the SDK under test. A tree-walk over the resolved lock found
four in logoscore-cli's closure and two per module build; with the overrides
applied the walk reports zero remaining.
WHAT WAS REMOVED, and why it was doing nothing:
* The `.../logos-cpp-sdk/logos-lidl` overrides added in
|
||
|
|
461cfed52d |
refactor: the LIDL codec exists once (#117)
* refactor: the LIDL codec exists once
The cdylib generator emitted its own copy of the codec — ~186 lines of
C++-emitting-C++ mirroring logos-protocol's logos_codec.h by hand. Every codec
fix had to be written twice or it silently only half-applied, which happened
twice in a row recently (routing scalars through the codec + signedness; then
accepting 3.0 while still rejecting 3.7).
It was worse than duplication. The two copies had DRIFTED — the emitted integer
decode gated on is_number() where the canonical one checked is_number_integer()
|| is_number_unsigned() — and logos_json.h's byte helpers were the same mangled
symbols with weak linkage and DIFFERENT bodies as logos_codec.h's, both reaching
one program (module TUs compiled one; liblogos_protocol.a carries TUs that
included the other). Which body won was down to link order.
logos_json.h goes back to its documented charter — "LogosMap/LogosList aliases
for impl classes", per its own CMakeLists — and loses 77 lines. jsonToBytes moves
beside its sibling jsonToStringVec in logos_lp_client.h, rebuilt on the canonical
isTaggedBytes/b64UrlDecode; it keeps its own narrow spelling because every lp
decoder is documented to yield the default-constructed value on a mismatch,
which neither bytesFromJson (throws) nor bytesFromJsonLenient (accepts more) does.
Emptying it rather than making it include logos_codec.h is deliberate: some
thirty alias-only include sites across the module repos get ZERO new includes,
and logos-cpp-sdkConfig's "only dependency is nlohmann_json" stays true.
With the clash gone the generic half is deletable. emitGeneratedCodec becomes
emitRecordCodecs: one logos::detail::Codec<::Rec, void> per declared record, and
nothing else. That residue is irreducible — a LIDL `type` is a per-contract
struct whose fields exist only in that module's header, and C++17 has no field
reflection. Nesting composes for free: Codec<std::vector<Blob>> and deeper come
from the shared half once Codec<::Blob> exists.
One asymmetry dies with it. The scalar bstr decode and the [bstr] element decode
were different functions with different strictness, so echoBytes("hi") succeeded
while echoBytesList(["hi"]) threw — inside one module, for the same type. They
are one function now.
Build wiring: ONE line, in this repo's own test CMake, using a variable
nix/tests.nix already supplies. Nothing in logos-module-builder, logos-qt-sdk, or
any module repo.
verified: cpp-sdk + protocol suites green; test_fullapi_cpp, test_fullapi_ext_cpp
and test_basic_module_cpp build; test-modules 176/176. Conformance delta is
exactly one cell, baselined first in logos-test-modules#31.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: bump logos-protocol to the path-threaded bstr decoder
logos-protocol 4359557 (#33). Required by this branch, not incidental: deleting
the emitted codec swaps its path-carrying bstr decode for the canonical one, and
without #33 the canonical one reported "at value" instead of "[0].payload" —
losing the diagnostic exactly where a malformed bstr is hardest to find.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ff8c3003a4 |
fix(cdylib): typed scalars go through the codec, and the emitted codec checks signedness (#115)
* 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>
|
||
|
|
7fbdfe26ac |
chore: bump logos-protocol to the uint64 fixes (#114)
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> |
||
|
|
2ac8e8fc17 |
chore: bump logos-protocol to the lp owner-thread fix (#28) (#110)
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> |
||
|
|
2e31eeb1ac |
chore: bump logos-protocol to the owner-thread destroy fix (#109)
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> |
||
|
|
f3de5527e2 |
chore: bump logos-protocol to master (rejection token re-exchange #26) (#108)
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> |
||
|
|
c3fa1b5ad3 |
chore: re-pin logos-protocol to master (LogosAPIConsumer handle cache) (#106)
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). |
||
|
|
ad30010f80 | chore: bump logos-protocol to master (nested-bytes qvariantToNlohmann fix #23) (#104) | ||
|
|
d12a7bbb45 |
chore: bump logos-protocol to include plaintext-args log fix (#98)
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> |
||
|
|
350a2891e6 | chore: bump logos-protocol (#97) | ||
|
|
31b9d2bf5f | chore: bump logos-protocol (#95) | ||
|
|
aea29d3797 |
Per-module concurrent dispatch: C++ module async export (#93)
* 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> |
||
|
|
1bc101df1f |
feat: cpp-generator consumes logos-lidl; delete embedded frontend (#89)
* 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> |
||
|
|
f0fe8cbfeb |
Make the base SDK Qt-free: Qt developer layer moves to logos-qt-sdk (#83)
* 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> |
||
|
|
38bc77e127 |
Consume logos-protocol: the transport/token/IPC layer moves behind the lp_* C ABI (#82)
* 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> |
||
|
|
25c88f4d48 |
support non-local remote transports (#57)
* support non-local remote transports * fix LogosResult * allow getting client over specific transport * fix ssl * investiage ssl error * pr comments * allow transport set configuration on any module * pr comments * add docs * pr comments * propagate only non-qt dependencies * restore ABI compatibility |
||
|
|
4b66dac015 | follow logos-nix for nix packages (#26) | ||
|
|
bb5498edef |
feat: add nix flake
feat: add nix flake test |