mirror of
https://github.com/logos-co/logos-view-module-runtime.git
synced 2026-08-27 11:01:13 +00:00
master
14
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
be25f5f95b |
feat(intents): add the frozen app-to-app intent surface
Three QML symbols, meant never to change:
logos.request(intent, params, callback)
logos.intentRequested(requestId, intent, params, requesterName)
logos.respond(requestId, ok, data, error)
request() returns void deliberately. The requester never receives a
requestId, so it cannot forge a respond() for its own request and cannot
hold a handle to whoever answered — the single property that lets the
router underneath be replaced without any app noticing.
LogosIntent.h carries the shared vocabulary (five error codes, the name
grammar, payload rules, the {ok,data,error} envelope) as a header-only,
non-QObject namespace, so a host's broker and this bridge can never
disagree about what a legal name or a legal error is.
LogosIntentRouter.h is the seam. The bridge does NO policy: no `uses`
check, no grammar check, no payload check, no provider lookup. All of
that belongs to the router implementation, which is expected to be
DELETED when the core runtime takes over provider selection. Policy
placed in the bridge would survive that deletion and turn removing it
into a migration for every installed app.
|
||
|
|
ba56a70b04 |
feat(ui-host): adopt the parent's credential through the shared verb (#27)
* feat(ui-host): adopt the parent's credential through the shared verb
ui-host is handed its parent's per-spawn credential on stdin and installs
it under both bootstrap keys. That was already the right shape — it is the
one place in the system that seeded a store with its OWN identity's
credential rather than the host's — but it spelled the bootstrap key set
out itself, making it the fifth such site.
It now goes through logos::adoptConsumerCredential, so
TokenManager::bootstrapKeys() owns the set. No behaviour change here: this
image's store IS the process ring, which is exactly the case that verb is
narrowed to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(lock): logos-plugin-qt -> master for logos_consumer.h, protocol to the rev master implements
WHAT MOVED
logos-plugin-qt ef11c210 -> 7cad5ec3 (master, #27 merged)
logos-protocol 79894727 -> b37a2e9f (0.5.0 -> 0.7.0)
WHY plugin-qt HAD TO MOVE
This branch's ui-host calls logos::adoptConsumerCredential and the bridge
takes a logos::ConsumerIdentity, both declared in cpp/logos_consumer.h. That
header did not exist at ef11c210, so every build here died at
LogosQmlBridge.h:17:10: fatal error: logos_consumer.h: No such file or directory
logos-plugin-qt#27 ("logos::admitConsumer -- one home for admitting a
non-module") merged as 7cad5ec3 and ships logos_consumer.h / .cpp. Relocking
that input is the whole fix for the reported red.
WHY protocol MOVED TO 0.7.0 AND NOT TO MASTER
logos-plugin-qt's logos-protocol input `follows` OURS, so this repo -- not
plugin-qt's own lock -- decides which protocol logos-qt-host is compiled
against. The first attempt took protocol to master (42460e5b, 0.8.0). It does
not build, and it does not fail in this repo:
logos-plugin-qt/cpp/logos_consumer.h:68:4: error: #error "logos-protocol is
newer than the consumer-admission contract this file implements. A private
token store is created empty; if the protocol changed how a consumer is
seeded, this file and the hosts calling logos::admitConsumer must move in the
SAME wave. Review adoptCredentialFor / bootstrapKeys, then raise this bound."
That is a deliberate upper bound in plugin-qt master:
#if defined(LOGOS_PROTOCOL_VERSION_MINOR) \
&& (LOGOS_PROTOCOL_VERSION_MAJOR > 0 \
|| (LOGOS_PROTOCOL_VERSION_MAJOR == 0 && LOGOS_PROTOCOL_VERSION_MINOR > 7))
Every other version guard in the fleet is a `>=` floor; this one is a ceiling,
written precisely so a protocol bump cannot outrun the consumer-admission
contract silently. protocol 42460e5b (0.8.0, the INBOUND/OUTBOUND direction
split) merged at 14:16:33Z and logos-plugin-qt#27 merged at 14:16:41Z, eight
seconds later, on top of 0.7 -- plugin-qt master's own flake.lock pins
logos-protocol b37a2e9f. So plugin-qt master implements the <= 0.7 contract and
no branch anywhere raises the bound yet.
b37a2e9f is therefore not a compromise, it is the rev plugin-qt master itself
locks: this closure is the one plugin-qt's own CI validates, and there is no
protocol split. Verified in the built closure:
logos-view-module-runtime compiles against logos-protocol 0.7.0
logos-qt-host-0.1.0 links logos-protocol-lib-0.7.0
logos-cpp-sdk contributes no protocol path at all
(header-only Qt-free types; its own 0.2.0
logos-protocol lock node never reaches the build)
Nothing in this repo references a 0.8-only symbol (no saveInboundToken, no
adoptCredential/adoptCredentialFor, no LOGOS_PROTOCOL_VERSION guard), so 0.8
buys this branch nothing today. flake.nix is untouched and the lock's
`original` for logos-protocol stays master-tracking -- once plugin-qt raises
the bound, a plain `nix flake lock --update-input logos-protocol` carries this
repo to 0.8 with no edit.
CHECKS -- every target the flake exposes for this builder, built individually
with --print-out-paths, upstream cache only (cache.nix.logos.co is 502), so
these are real from-source builds:
packages.x86_64-linux.default OK
/nix/store/gajjq3050m46sprbiqqz0c32mhi95vb7-logos-view-module-runtime-1.0.0
checks.x86_64-linux.default OK
/nix/store/9a4wvnp6b226s2lam54dgxmzm86r9j58-logos-view-module-runtime-check-1.0.0
packages.x86_64-linux.tests OK (same derivation as the check)
/nix/store/9a4wvnp6b226s2lam54dgxmzm86r9j58-logos-view-module-runtime-check-1.0.0
checks.x86_64-windows.default OK (cross to mingw; compile only, nix
forces doCheck off for a cross build,
so no ctest ran there)
/nix/store/csp4n8hn0xrf2j2lxflv6k8affbn180v-logos-view-module-runtime-check-x86_64-w64-mingw32-1.0.0
packages.x86_64-windows.default OK (bin/ui-host.exe is a real PE32+)
/nix/store/s8y4s43nfl8932hnffg645bzn9dqy5fs-logos-view-module-runtime-x86_64-w64-mingw32-1.0.0
checks.x86_64-linux.default runs the suite in its checkPhase:
Start 1: LogosQmlBridgeUnitTests Start 6: BridgeIdentityTests
Start 2: LogosQmlBridgeE2ETests Start 7: LogosQmlBridgeDeferredTests
Start 3: LogosQmlBridgeGuiTests Start 8: LogosQmlBridgeCallsTests
Start 4: LogosQmlBridgeResultTests Start 9: LogosQmlBridgeReplayTests
Start 5: LogosQmlBridgeHandshakeTests Start 10: UiHostUnloadTests
100% tests passed, 0 tests failed out of 10
Total Test time (real) = 39.36 sec
BridgeIdentityTests is the suite this branch adds, so admitConsumer /
adoptConsumerCredential are exercised, not merely linked.
NOT VERIFIED: the aarch64-* and *-darwin outputs. This builder is x86_64-linux
and there is no darwin remote; CI's macos-latest leg covers them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(lock): protocol 0.8 + plugin-qt master, and un-collapse the fixture's two token stores
WHAT MOVED
logos-protocol b37a2e9f (0.7.0) -> 42460e5b (0.8.0, "separate INBOUND from OUTBOUND")
logos-plugin-qt 7cad5ec3 -> 048152f2 (plugin-qt#26)
Six lines of flake.lock; the lock stays at 14 nodes. logos-plugin-qt's
logos-protocol input `follows` OURS, so this repo decides which protocol
logos-qt-host is compiled against, and both halves have to move together.
THIS SUPERSEDES e370ce5's REASONING, WHICH WAS CORRECT WHEN WRITTEN
|
||
|
|
b9a6778fff |
docs: the module-side templates live in logos-view-module now (#25)
* feat(bridge): a QML view's bridge carries ITS identity, not the host's The QML a view module ships runs inside the host process, and the bridge it was handed was the host's own LogosAPI — which carries the host's ambient token ring, holding every loaded module's root auth token. A view could reach any module in the system with no requestModule in the log at all. LogosQmlBridge::forIdentity(name) builds the bridge on a LogosAPI bound to that identity's ISOLATED store, and returns nullptr rather than falling back to the host's authority. tokenStore() exposes the store a bridge presents from, because that — not the name — is the identity that decides anything. tests/test_bridge_identity.cpp is the acceptance test: two real published providers, an ambient ring seeded the way module_manager seeds it, and a capability_module that mints only for declared (origin, target) pairs. A host-identity bridge reaches an undeclared backend with requestModuleCalls == 0; an identity bridge is refused. * feat(b4): link the Qt host runtime from logos-plugin-qt, not logos-qt-sdk The B1 split moved the Qt host runtime — LogosAPI and the provider objects — out of logos-qt-sdk into logos-plugin-qt, which publishes it as packages.<sys>.logos-qt-host with the CMake target logos-qt-host::logos_qt_host. This repo now consumes that target directly, so B2b can delete the forwarding shim in logos-qt-sdk. logos-qt-sdk is dropped as an input entirely rather than kept alongside: the host runtime was the only thing this repo ever took from it. logos_api.h is the single qt-sdk-provided header anything here includes; every other non-Qt header on the include lines (token_manager.h, logos_api_client.h, module_proxy.h, remote_transport.h, logos_instance.h, logos_mode.h, logos_types.h, logos_json_convert.h, logos_call_error.h, logos_object.h, logos_provider_interface.h) comes from logos-protocol, which logos-qt-host links PUBLIC. Nothing here touches the surface that stays behind in qt-sdk — no logos_ui_plugin_context.h (that is for ui_qml module backends, not for the host that loads them), no logos_qt_lp_bridge.h / logos_qt_wire.h, no logos-qt-generator. LOGOS_QT_SDK_ROOT becomes LOGOS_QT_HOST_ROOT, matching what logos-standalone-app already passes. Because a find_package that resolves to the wrong prefix would leave the target undefined and every LogosAPI symbol unresolved at link time — an error a long way from its cause — the import is followed by an explicit TARGET check that is a FATAL_ERROR, never a skip. Evidence the swap is a no-op for the produced artifacts: with the same inputs, lib/liblogos_view_module_runtime.a is byte-identical (md5 2e205e1668f57f59fd5ccfc005a26600) before and after, bin/.ui-host-wrapped is byte-identical, the installed headers are identical, and the 317 LogosAPI symbols in the ui-host binary are unchanged. The only difference in the derivation is that logos-qt-sdk.drv left the input closure and logos-qt-host-0.1.0.drv took its place. checks.default runs the same six ctest cases (LogosQmlBridgeUnitTests, LogosQmlBridgeE2ETests, LogosQmlBridgeGuiTests, LogosQmlBridgeResultTests, LogosQmlBridgeHandshakeTests, BridgeIdentityTests), 6/6 passing on both sides. * docs: name the check that holds these headers to the module side LogosViewPlugin and LogosViewReplicaFactory are the HOST half of a pair. The module half is declared separately, by logos-plugin-qt's LogosView*.in templates, and the two cannot share a header: a module plugin must compile against Qt alone, and this repo depends on logos-plugin-qt, so the include could only ever point the wrong way. They bind at runtime through the IID string, where a mismatch is silent — qobject_cast returns nullptr and the view never appears. The module-side copy used to carry a comment asking whoever edited it to keep these files in sync. It pointed at src/; the files are in include/. That is what a sync comment is worth. logos-module-builder — the one repo that depends on both sides — now runs a `view-interface-abi` check in CI comparing the IID and the pure-virtual list. These headers say so, so an editor of this half knows what will catch them. No code change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: say what the ABI check reads, and that nothing here runs it The note pointed at logos-module-builder's `view-interface-abi` and described it as comparing "the IID and the pure-virtual list". That undersold it and, more importantly, it did not say that the argument of Q_DECLARE_INTERFACE below is itself compared after resolution — that macro argument is the string qobject_cast matches on, it does not have to be the #define above it, and changing just that one token is a real divergence. Also states the thing that is easy to miss from inside this repo: there is no .github here, so nothing checks these headers at the moment they are edited. The check first sees a change when logos-module-builder bumps its pin. Editing this header is a two-repo change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(deps): rev-pin logos-plugin-qt and logos-protocol at the pushed tips Both inputs were master-tracking, and neither master can build this repo after the qt-host retarget: * logos-plugin-qt's master (8846fc5) has no `logos-qt-host` package at all — evaluation died with "attribute 'logos-qt-host' missing" at flake.nix:48. cc24fa1 is the tip of that repo's feat/b4-qt-host-windows-target, already rebased onto its master, and is the SUPERSET of the two branches carrying the qt-host work: the sibling feat/b4-qt-host-windows-target-8ccb1fc (989f6ae) drops the commits logos-module-builder pins. Pinning the superset is what keeps ONE logos-qt-host in the downstream closure rather than two. * logos-protocol had to move with it. logos-qt-host calls TokenManager::forIdentity / isolateIdentity, which are on feat/per-client-token-store (c8bab12) and not on protocol master — and because logos-plugin-qt's logos-protocol `follows` THIS input, leaving it master-tracking would have built the Qt host runtime against a protocol lacking those symbols. c8bab12 is a fast-forward from master, so nothing is given up. Both revs are pushed branch tips, not local-only commits. Drop the revs once the branches merge. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(tests): retarget the two tests master added onto logos-qt-host Rebasing this branch onto master replayed the qt-host retarget over a master that had meanwhile added test_logos_qml_bridge_deferred (#20) and test_logos_qml_bridge_calls (#21). Both link logos-qt-sdk::logos_qt_sdk, and the retarget commit never saw them, so the textual auto-merge left them pointing at a target this repo no longer imports. CMake failed the generate step outright: Target "test_logos_qml_bridge_calls" links to: logos-qt-sdk::logos_qt_sdk but the target was not found. Retargeted to logos-qt-host::logos_qt_host, the same rename the other eight executables already carry. Restoring the logos-qt-sdk input was the wrong fix in the other direction: it would put a second copy of the Qt host runtime back in the closure, which is what the retarget exists to prevent. Both tests only include logos_api.h (now from qt-host) and the protocol headers qt-host links PUBLIC, so nothing qt-sdk-only is lost. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs: the module-side templates live in logos-view-module now Both host-side interface headers named logos-plugin-qt/cmake/LogosView*.h.in, a path that no longer exists — the view-authoring concerns moved to logos-view-module. LogosViewReplicaFactory.h also justified the split with "that repo is upstream of this one, so the two cannot share a header". That was true of logos-plugin-qt, which this repo really does depend on. It is false of logos-view-module: there is no edge between the two in either direction, which is the actual reason they cannot share a header, and is what logos-module-builder's view-interface-abi check now says. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(deps): track protocol and plugin-qt master, and fix the check that was red logos-protocol#59 and logos-plugin-qt#19 merged, so both rev pins are retired. Also repairs this repo's ONLY check, which was already red before the pin work and would have kept the PR red regardless: tests/CMakeLists.txt linked logos-qt-sdk::logos_qt_sdk, a target that cannot exist since the split removed logos-qt-sdk as an input here. The other ten test targets already linked logos-qt-host::logos_qt_host — |
||
|
|
0d9c677bd6 | fix: fix the hot relaods | ||
|
|
471dd56640 |
feat(windows): cross target, and quit ui-host with WM_QUIT instead of hard-killing it (#23)
* feat(windows): wire the view-module-runtime for x86_64-windows Routes through logos-nix.lib.forAllTargets, widens meta.platforms, and picks up Qt's host-tool cmake flags (empty natively). wrapQtAppsHook is GATED rather than removed: it fails to EVALUATE for a mingw host, and wrap-qt-apps-hook.sh would skip a PE anyway (`isELF || isMachO || continue`) -- but removing it outright would change native behaviour. dontWrapQtApps = true is the mandatory other half, or qtbase's own setup hook hard-errors with "depends on qtbase, but no wrapping behavior was specified". Windows and native (aarch64-darwin) both evaluate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(windows): quit ui-host with WM_QUIT instead of hard-killing it QProcess::terminate() cannot stop ui-host on Windows, so every UI-plugin teardown paid the full 3s grace period and then TerminateProcess. MEASURED, not inferred. terminate() on Windows is EnumWindows(WM_CLOSE) plus PostThreadMessage(tid, WM_CLOSE) (qprocess_win.cpp:648-653). ui-host owns ZERO windows -- it is a bare QCoreApplication linking no Qt6::Gui at all (ui-host/main.cpp:70, CMakeLists.txt:78-86); the QQuickWidget lives in the PARENT and ui-host only remotes the plugin object over QtRO. So the enumeration finds nothing. A controlled zero, not a broken command: the same enumeration in the same run found 7 windows for Basecamp, including the two never-shown QPA observer windows a GUI process must own. The thread message IS delivered, but Qt's dispatcher only branches on WM_QUIT (qeventdispatcher_win.cpp:545-548) -- WM_CLOSE falls through to a no-op for a thread message. terminate() returns void, so it "succeeds" having done nothing. A/B against the shipped ui-host.exe: replicating terminate() byte-exactly left it ALIVE past the full 3000ms; PostThreadMessage(WM_QUIT) exited it in 8ms with code 0. End to end through the real teardown path (graceful Basecamp quit with package_manager_ui loaded), same harness and trigger, only the payload differing: shipped ui-host exit 3905ms code 62097 (0xF291 Qt kill) "did not exit gracefully" present fixed ui-host exit 690ms code 0 warning absent (Basecamp total 4395ms -> 1186ms) REPLACES terminate() on Windows rather than preceding it. An earlier draft inserted WM_QUIT above the existing terminate()+3000+kill+1000, which would have made the worst case 6000ms -- worse than today while claiming to improve it. Since terminate() was measured to be dead time here, keeping it buys nothing: the ceiling stays 4000ms and the typical case is now sub-second. This also matches logos-container-subprocess, which REPLACES its equally no-op request_exit() rather than supplementing it. The POSIX path is byte-identical -- the original block is preserved verbatim inside #else; git diff shows zero removed lines. Getting the child's main THREAD id is the one subtle part. QProcess does not expose it, but CreateProcessArguments carries the PROCESS_INFORMATION pointer it hands to CreateProcess. The modifier runs BEFORE CreateProcess, so dwThreadId is not populated yet: capture the pointer there, read through it once started() has fired, and null it immediately -- QProcess owns that allocation and frees it in cleanup(). No CreateProcessW reimplementation and no Toolhelp32 snapshot needed. NOT fixed here, deliberately: orphan reaping is absent on Windows. ui-host's setsid + PR_SET_PDEATHSIG + getppid watchdog are wholly inside #ifndef _WIN32 (ui-host/main.cpp:40-68) and ViewModuleHost assigns no Job Object, so a Basecamp crash leaks ui-host.exe. That is a real second defect, but it is an untested process-lifetime change and does not belong bundled into a latency fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(deps): re-pin the L1-L4 inputs to their merged revs logos-nix (L1), logos-protocol (L2), logos-cpp-sdk (L3) and logos-qt-sdk (L4) are all 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> |
||
|
|
d26550f157 |
fix(qml-bridge): stop callModule/callModuleAsync failing on a module that is merely starting (#21)
* fix(qml-bridge): stop callModule/callModuleAsync failing on a module that is merely starting
Both carried the same `if (!client->isConnected())` guard the event path did,
and QML issues calls from the same place it subscribes — Component.onCompleted,
the one moment the dependency's host has been spawned but has not called
listen(). The guard was dead code for years (isConnected() returned a latch that
was always true), so nothing exercised what it does when it goes live: it
returns {"error":"Module not connected"} to a view that will never retry, for
the life of the process.
Deleting the guard alone is not the fix either. Without it the call falls
through to the transport's default acquire budget — 20 s, paid twice because the
token handshake tries capability_module first — on the GUI thread. That is the
~417 s Basecamp stall the isConnected() fix exists to prevent.
The two forms owe their callers different things, so they get different answers:
* callModuleAsync() owes a CALLBACK, so it can wait. A module that is still
starting is no longer an error: the call is held via the protocol's new
whenObjectAvailable() and dispatched when the module appears, including one
installed mid-session. Nothing blocks. The deadline the caller already passes
(timeoutMs) now bounds the wait for the module as well as the call, so a
module that never appears still gets an answer instead of hanging politely.
* callModule() owes a RETURN VALUE now, so it cannot wait for a module at all.
It waits a short bounded time (1500 ms — QtRO retries its endpoint every
250 ms and a starting module arms in roughly 50-150 ms, so this covers the
real race several times over) and then answers. Its error now says "Module
not reachable yet" and names callModuleAsync, because for a view whose first
paint depends on the answer the async form is the correct tool, not a longer
timeout here.
The reachability question is asked once, ahead of both failure shapes: an absent
module surfaces as either an acquire CallError or an invalid result depending on
transport and timing, and inferring "still starting" from which shape came back
is what made a startup race look like a broken module.
tests/test_logos_qml_bridge_calls.cpp is new, and is the first coverage this
path has had in either direction. It pins the two contracts separately, drives
the async form through a real QJSEngine (a non-callable QJSValue is silently
ignored, so a C++ lambda would not exercise what QML does), and asserts the
SHAPE of each failure rather than just that one occurred — "some error payload"
is what the broken version returned too. With the guards restored, three cases
go red, two of them reporting the old {"error":"Module not connected"} verbatim.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(qml-bridge): scope the publish timer so it cannot fire on dead locals
syncCall_modulePublishedDuringTheWait used QTimer::singleShot with by-reference
captures of two function locals. That is only safe while the call under test
waits long enough for the timer to fire inside it — which is exactly the
property the test exists to check, so it does not hold on the un-fixed tree the
test is supposed to go red against. There the call returns immediately, the
function exits, and the detached timer later constructs a Publisher through
dangling references.
Not hypothetical: it segfaulted the red run (`enableRemoting() Error: Unable to
Replicate an object that does not have objectName() set`, empty module name,
then SIGSEGV), and took the four cases after it down with it — so the red run
could not report on the very case that matters most.
The timer is now a scoped QTimer declared after `pub`, so it is destroyed first
and cancels any pending fire while its captures are still alive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(qml-bridge): three defects in the call-path change, and the tests that were missing
A compatibility audit found three bugs in the previous commit. All three shipped
green, because that commit touched one source file and no test file — so the 7/7
it reported said nothing about any of them. Each fix below now has a test that
was shown to fail against the un-fixed tree and pass with it.
1. A TIMED-OUT CALL WAS STILL SENT. The whenObjectAvailable callback ran
dispatch() unconditionally; the `fired` flag suppressed only the CALLBACK. So
an install / send / transfer could execute minutes after the view had been
told it timed out. Holding a call is only at-most-once if the hold is
abandoned when the caller stops waiting — otherwise it is a delayed re-send
with extra steps. The new test asserts on the PROVIDER's call count, not the
callback, because "suppressed" and "never sent" are indistinguishable from
the callback and only the second is the property claimed.
2. timeoutMs <= 0 COULD STRAND THE CALLER. Documented as "pass 0 to disable".
With no deadline and an unbounded wait for the module, the callback never
fired at all. Dispatching immediately instead — the first attempt — was worse:
it reaches the synchronous acquire inside invokeRemoteMethodAsync and blocks
the calling thread for the full default budget, which is the GUI-thread stall
this whole area exists to remove, smuggled back in through the one path that
opted out of deadlines. "No deadline on the call" now means exactly that: the
WAIT still carries one, used only to guarantee a callback, never to cap the
method. The test deliberately never publishes the module, because the earlier
version of it published one and therefore passed against the broken code too.
3. THE STARTUP BUDGET CAPPED EXECUTION. One Timeout feeds both
acquireCachedObject and callMethod, so Timeout(1500) capped every method, not
just the acquire — a network fetch, a package install, a chain RPC all began
failing at 1500 ms, and old QML shipped inside a .lgx cannot opt out because
callModule takes no timeout argument. The short budget now applies only when
the module is NOT reachable, which is the case it was written for; a reachable
module keeps the budget it always had.
7/7 green, and 3/3 of the new cases red against the previous commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(deps): re-pin logos-protocol to 0183e8c (#53 + #55)
This branch calls whenObjectAvailable(), which landed in logos-protocol#53. The
lock inherited 07b0fb1 from #20's re-pin, and that revision does not have the
symbol -- the build fails to compile, not to link.
Pinned to 0183e8c rather than to #53's merge commit on purpose. This PR's
callModule advertises a 1500ms bound, and until logos-protocol#55 that bound was
not real: the capability handshake runs FIRST on an un-tokened target and
hardcoded 20000 twice, so a first call could block on the order of 40s before the
part this PR bounds even began. #55 threads the caller's budget through, and
0183e8c is the first master revision that has it. Pinning below it would ship a
guarantee its own dependency does not provide.
VERIFIED FROM THE LOCK, driving this repo's own flake with no override of any
kind -- the workspace flake supplies logos-protocol through `follows` and would
answer for a pin this repo does not use:
nix build 'path:./#checks.aarch64-darwin.default'
100% tests passed, 0 tests failed out of 7
Seven now: LogosQmlBridgeCallsTests joins the six that #20 runs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
31bbfad97b |
fix(qml-bridge): defer onModuleEvent instead of refusing it once (#20)
* fix(qml-bridge): defer onModuleEvent instead of refusing it once
A QML plugin subscribes in Component.onCompleted, which runs while the
view is being built -- in Basecamp that is immediately after
PluginLoader spawned the core dependency's host process and well before
that process called listen(). onModuleEvent asked `isConnected()`
anyway, warned once, returned false, and never tried again for the life
of the process. Method calls kept working (they reach the replica by a
path that never asks), so this read as "QML events are broken".
The measured shape, on Windows but platform-independent:
RemoteTransportConnection: Registry connect attempt started
LogosQmlBridge::onModuleEvent: "hello_module" not connected <- same ms
... 45 s later ...
RemoteTransportConnection: Requesting object: "hello_module" <- the CALL works
Now: no isConnected() probe, no requestObject(), nothing on this path
that can block the GUI thread. The subscription goes to
LogosAPIClient::onEventWhenAvailable and arms when the module becomes
reachable, including a module installed mid-session by the package
manager.
Return-value contract: true now means ACCEPTED, not live. false is kept
only for errors no retry can fix -- no LogosAPI (the existing null-API
test still asserts that), an empty name, or a VIEW module (whose signals
come off its typed replica). Every caller in the workspace was checked:
nothing in production reads the value; the only site that did was
test_2proc_bridge_driver, whose "first subscription proves the
connection is up" probe is replaced by pendingEventSubscriptions()
draining.
De-duplication lives here rather than in the transport, because
lp_subscribe legitimately allows two subscriptions to one event while
QML re-running Component.onCompleted must not double-deliver.
tests/test_logos_qml_bridge_deferred.cpp is the regression guard:
subscribe-before-publish (the bug), publish-before-subscribe (control,
green either way), duplicate-subscribe (was delivering twice), and a
non-blocking budget so a future "just call requestObject() from the
retry" cannot creep back in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(qml-bridge): verify the de-dupe record instead of trusting it
onModuleEvent kept a set of (module, event) pairs it had already subscribed and
short-circuited on it. The set was only ever cleared on the abandon path, so any
other way a subscription could stop being tracked left the bridge believing it
was live: a QML view re-calling logos.onModuleEvent got `true` back and nothing
armed — a permanently silent success, which is the original bug wearing the
fix's clothes.
It now records the subscription id and checks it against
LogosAPIClient::eventSubscriptionState() before short-circuiting. Unknown means
the registry is not tracking it, so the call falls through and re-arms.
Also states on the API what it does not promise: arming is not retroactive and
no transport buffers, so a module that emits a one-shot event synchronously
inside its own init() can still be missed by a view subscribing in
Component.onCompleted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* test(qml-bridge): make the stale-record case actually reproduce the defect
The first version cancelled an ARMED subscription, which by design leaves its
callback attached to the shared handle -- so events kept arriving and the test
passed against the trusting-the-record version too. It now makes the record
stale while the subscription is still PENDING, where nothing is attached, so a
re-subscribe that gets swallowed as a duplicate delivers nothing and the test
goes red. Asserts the cancel hit the right id rather than assuming it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(qml-bridge): answer the three review comments on onModuleEvent
All three were right.
The VIEW-module warning was streamed across << operands with embedded quotes,
and QDebug quotes every QString it is given -- so the one part of the message a
reader is meant to copy came out as logos.module(" "chat_module" "). One
formatted string with noquote(), and the reason recorded at the call site,
because the streamed form looks correct in the source. Deliberately not applied
to the sibling warnings, where << moduleName << eventName WANTS the quoting: it
is what tells an empty name from a missing one.
The "roughly 50-150 ms" arming window was measured on one machine and read as a
contract. Now described as brief and load-dependent, which keeps the two facts a
caller can act on and drops the one they cannot rely on. The advice underneath
is unchanged and is what matters: a module whose one-shot startup event matters
must also expose a method the view can call after subscribing.
The return contract listed three cases; the implementation has five. Both
missing ones are documented rather than tightened away -- "no client for the
module" is LogosAPI failing to build one at all, not the module being down, and
the id == 0 refusal is a guard whose only job is keeping two contracts in
agreement. Deleting it would remove the thing that notices when they stop
agreeing. The comment now also states what was implicit and matters more than
the list: a module that is merely unreachable is still an ACCEPTANCE.
Comment-only apart from the warning's formatting.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(deps): re-pin logos-protocol to 07b0fb1 (#47)
This branch calls onEventWhenAvailable, cancelEventSubscription,
eventSubscriptionState and pendingEventSubscriptions. The lock pinned 0f26ffd,
which has none of them, so every green run of this branch until now was produced
with --override-input and the lock itself had never resolved.
07b0fb1 is #47's merge commit. Its narHash is identical to the branch tip the
bridge was verified against, so this pin is byte-for-byte the tree those runs
used, not merely a compatible one.
VERIFIED FROM THE LOCK, which is the part that was missing:
nix build 'path:./#checks.aarch64-darwin.default' # no override of any kind
100% tests passed, 0 tests failed out of 6
Driving the repo's OWN flake matters here. Building the workspace flake's
logos-view-module-runtime target does not test this lock at all: the workspace
supplies logos-protocol to every consumer through `follows`, so it answers a
different question and will happily go green (or red) on a pin this repo does not
use. The first attempt at this verification made exactly that mistake and failed
with "no member named 'onEventWhenAvailable'" while the lock was already correct.
Note the closure still holds two logos-protocol revisions: root and logos-qt-sdk
resolve to 07b0fb1, while logos-cpp-sdk keeps its own, because only qt-sdk
declares `inputs.logos-protocol.follows`. That is the configuration verified
above and it builds clean. Adding the missing follows to logos-cpp-sdk would
collapse it to one revision and is worth doing -- separately, since it changes a
configuration nothing has tested yet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
8085c22a18 | feat: add pre fetch to model | ||
|
|
96735cb9c8 | fix: Pass per-spawn auth token to ui-host via private socket | ||
|
|
d611d697bf |
Revert "fix: handle long module names"
This reverts commit
|
||
|
|
1b35afb543 | fix: handle long module names | ||
|
|
5fcf427b49 | feat: let qml only apps subscribe and listen to events from core modules | ||
|
|
b4d5cb70f0 | move watch as function in bridge so qml doesnt need to make new imports | ||
|
|
d9fa197f87 | feat: add src headers and flake files needed |