Logoscore Weekly Update — 2026-09-14
Highlights
- Optional dependencies landed, from the LIDL grammar to the app. A third dependency kind, between
dependencies(auto-loaded, required) andinterface_dependencies(bound to a name at runtime): concrete modules a module may call and does not require. It went in as a deliberate chain — keyword, metadata decode, manifest 0.6.0,.lgxbundler, installer resolution, module-ABI projection, load semantics, the SDKs, and finally Basecamp, logosctl and the standalone app asking for them by default. The feature is the separation held at every layer: the loader never auto-loads them, never reports one asmissing, and never fails a load over one logos-lidl#11, logos-module#26, logos-package#39, logos-package-manager#42, logos-liblogos#205, #207, logos-basecamp#409 - The shell’s intent surface got a namespace, a written rule for what may join it, and two more providers.
logos.*is reserved for the platform and enforced in the frozenLogosIntent.h;basecamp.*is this shell’s and enforced inIntentRegistry— because which shell owns which prefix is policy, and policy belongs in the disposable half, so another frontend reserves its own prefix without touching the frozen header. The shell now provides four navigation intents and three package dialogs, and the line between those groups is a security property rather than a taste one: the broker skips the chooser when the shell is the only provider, so everything in the navigation group is something a requester can make the shell do with no consent dialog at all.basecamp.apps.launchtakes the app name as a parameter and answers constantly whether or not that app exists, because a differential answer is an installed-app enumeration oracle — iterate plausible names, read the answers, recover the user’s whole app list.confirm_uninstallandconfirm_upgradeare restricted topackage_manager_ui, since an unsolicited prompt to remove one of your packages has no context to judge against and its correct answer is always no — a dialog whose right answer is unconditional can only train dismissal. No aliases were carried for the oldlogos.*spellings: restrictions are keyed on the name as submitted, so a survivinglogos.packages.confirm_uninstallwould be a second live path to a restricted, destructive capability logos-basecamp#390, logos-package-manager-ui#78, #79 - Two repositories can publish the same display name, and one in the wild does. A fork of the official catalog left the two indistinguishable in the app manager and in Settings, and made an install from one appear to be running in the other. Fixed end to end across four repos: source information added at the downloader, carried across the module ABI, and surfaced in both frontends — a repository is labelled with its source only when more than one claims its name (“Logos Official (logos-co)” vs “(0x-r4bbit)”), derived once in
PackageCoordinatorso every view agrees, with a Settings warning on collision and in-flight install state scoped to the repository the operation targets, so only the row acted on shows it logos-package-downloader#38, logos-package-downloader-module#37, #38, logos-package-manager-ui#81, logos-basecamp#414 __logos_call_complete__stopped being part of every module’s event surface. It carries a deferred call’s return value and rode the same channel a module uses for its own events, so a consumer could ask for it by name and a wildcard subscriber received every one —logosctl watch <module>with no--eventis the reachable form.logos-evm-keystore-cli’s README already documented the consequence, as a platform property rather than a bug: acreate_mnemonicreply is a recovery phrase. Reserved now on all three transports, atonEvent()and in wildcard fan-out, because either half alone leaves the other route open logos-protocol#87, #88- Basecamp is a real Windows application.
LogosBasecamp.exelinks as a GUI-subsystem PE, and the two child hosts spawn withCREATE_NO_WINDOW— because a parent with no console of its own makes Windows allocate a fresh console, and a console window, per child. Measured on Windows 11 with four modules loaded: 10 visible console windows before, 2 after, which is the user’s own terminal. The.zipis gone, replaced by an NSIS installer offering a per-user install or extract-only portable mode: 113 MB → 51 MB logos-basecamp#411, logos-container-subprocess#8, logos-view-module-runtime#32 - One protocol and one qt-host per closure became a check rather than a convention.
logos-qt-hostbakessizeof(LogosAPIClient)into its ownoperator new;logos-protocoldefines that constructor — and #85 took the object 88 → 96 bytes. Splicingoperator new(88)onto a 96-byte object is silent on macOS (88 rounds into the 96-byte size class, so the overrun lands in allocator slack) andmalloc(): corrupted top sizeon glibc, attributed to an unrelated later allocation.abi-closure-checkships from protocol, because only a consumer’s finished closure holds both halves logos-protocol#86, logos-liblogos#212, #214 - The signer sheet became legible. Three numbered sections — who asked, what you are signing, what this signer makes of section 2 — so the distinction the whole design rests on is stated rather than inferred from styling. The keystore now returns the requester’s claim as its own list, so an app can no longer write on the signing screen in the keystore’s typeface; and a token list, declared optional and bounded at 2 s, names the address in the leg — except over a VERIFIED address, where a weaker claim beside a stronger one is a question the reader must settle rather than an answer logos-evm-keystore-module#10, logos-evm-signer-ui#12, #8, #14
- A three-second timer was failing the signing flow, and the number was not the fix. Press Send, the wallet raises
evm.signing.approve, the shell puts up its chooser — and a couple of seconds spent choosing returnedexpired_no_ackfor a signer that was seconds from answering. A timer there cannot tell “nobody is coming” from “someone is coming, slowly”; at 3 s it answered the second wrongly, at 60 s it would still hang for a minute on the crashed signer it exists to catch. The party that knows is the requester, whose dispatch completes or fails exactly once — so prompt cleanup moved there, and what stayed is GC for the one case a requester cannot clean up after: its own death logos-evm-keystore-module#9 - A user repository with 14 packages listed nothing in Basecamp, and every diagnostic reported success.
providesis an array of intent objects;value(key, "")callsget<std::string>()on it and throws, and thetrywrapped the entire package loop — so one package carrying intents discarded every package in that repository. The official catalog has noprovideson any manifest, which is why only third-party repos were affected and why it read as a network problem. The catch moved inside the loop, so the next field added there cannot cost a repository logos-package-downloader#39 - The welcome page became the way into the app. Search across apps and packages with a type filter and
Ctrl+Kto focus it, results that open, install or hand off to the full list, and a recently-closed row backed by a store that persists across launches rather than only within a session — plus aShortcutBridgethat routes shortcuts to the focused QML pane, since a workspace ofQQuickWidgetdocks otherwise swallows them. It then became a proper workspace tab, with its visibility synced against the open docks instead of inferred logos-basecamp#388, #391 - RLN became pluggable, configurable and visible. The delivery library’s RLN plugin is implementation-agnostic — it carries no configuration, names no registry on its callbacks and does not start the backend — so this module took all of it on, and the demo now configures RLN and shows membership, epoch quota, the epoch countdown and live proof/validation counts across
createNodelogos-delivery-module#94, #100, #105, logos-delivery-demo#27
Initiatives
Optional dependencies — one kind, held apart at every layer
A module can now name what it talks to in three ways, differing in who picks the module and who guarantees it is running, not in how you call it. The chain was built bottom-up, each link inert until the one below it landed.
- The grammar and the schema.
optional_dependsjoins the LIDL grammar, AST, serializer and JSON round-trip — emitted only when non-empty, so every contract written before the keyword existed serialises byte-for-byte as it did. Folding them intodependswould have been worse than dropping them:lidl_gen_clientwritesdependsback out asdependencies, so a folded optional dependency round-trips into a required one, which is exactly the property declaring it optional exists to prevent logos-lidl#11, logos-module-builder#228 - Typed, but never bundled. Optional deps join
staticDeps— the typed wrapper generated from the dependency’s publishedpackages.<sys>.lidl, no dep build — and nevercollectAllModuleDeps. That is precisely the arrangementeth_rpc_modulehad been hand-rolling, withdependencies: [], untyped calls and a hard-coded 1500 ms probe budget written twice, to keep the whole nimbus closure out of the multi-chain wallet and keep the EVM stack on Windows. Measured: that dependency’s LIDL closure is one 19,928-byte file with zero nimbus paths logos-module-builder#228, logos-evm-eth-rpc-module#11, #12 - A second edge set, never merged. The load closure, the teardown cascade and the missing-dependency verdict all read
dependencies, and every one of them must ignore this one. Optional edges are soft in the resolver: they order modules already in the set, never expand it, and one that would close a cycle is dropped — breaking a cycle is what optional dependencies are for, so reporting one as a cycle would refuse the configuration the feature exists to allow logos-module#26, logos-liblogos#205 - Best-effort loading, with “tolerable to fail” computed by subtraction.
LOGOS_LOAD_REQUIRED_AND_OPTIONALbrings up whichever optional dependencies are installed, ordered ahead of the module that names them. ThebestEffortset is derived by re-walking the required graph and subtracting, not by tagging nodes as the queue reaches them — a module can be required by one thing and optional to another, and whichever edge the queue happened to reach first must not decide whether a genuinely required failure is fatal. The first cut got the transitive case wrong and the fix is the second commit: expanding optional edges inline in the required BFS made an uninstalled namemissing, soapp -opt-> extra -req-> ghostfailed the load ofappoutright logos-liblogos#207 - The
boolbecame an enum, and the values are pinned.logos_core_load_module(name, bool)→(name, LogosLoadDeps), because two independent bools would admit “best-effort optional but don’t resolve dependencies”, which does not exist. Under C linkage the symbol mangles identically whatever the parameter type, so a consumer that hand-copies the prototype compiles and links with no diagnostic anywhere — pinning 0 and 1 to the oldfalse/truemakes such a consumer stale rather than broken logos-liblogos#207, logos-cpp-sdk#158, logos-qt-sdk#51, logos-module-builder#235, logos-liblogos#211 - Packaging and installation.
manifest.jsonis the only copy an installer or catalog can read before the package is unpacked, so both new kinds reach it —optional_dependenciescopied through,interface_dependenciestransformed to bare names, because the author’s object form carries paths into flake inputs that mean nothing in a built package and logos-package rejects it rather than silently truncating. ManifestCURRENT_VERSION0.5.0 → 0.6.0; widening 0.5.0 in place would leave two documents claiming one version with different key sets logos-package#39, nix-bundle-lgx#14, logos-module-builder#229, nix-bundle-logos-module-install#7 - The installer marks the edge, not the package.
DependencyTreeNode.optionalis per-edge, with two conservative propagation rules: a required child of an optional edge inherits the mark, andflatten()’s dedup collapses with AND, so reachable by any required edge means required. ANotInstallednode carrying the mark is not a broken install logos-package-manager#42, logos-package-manager-module#67, #68, #69 - The projection that made it real.
toLogosMapcopieddependenciesand droppedoptionalDependencies, so every consumer of the module ABI was blind to an optional edge the manifest declared. Basecamp reads that map and nothing else — the effect was not “loaded late” or “degraded”, the module was never loaded at all:Module loaded: keystore_module…(token_list_module: absent)logos-package-manager-module#68, logos-basecamp#409 - And the gate that had to stop blocking. An absent optional entry made a plugin unlaunchable:
splitDependencyRowscounted everynot_installedrow as a blocker, so the user got a popup naming a dependency nothing requires. The exemption is not special-cased tonot_installed— the resolver never consults an optional edge when deciding load success, so one installed at a rejected version loads exactly as an absent one does, and blocking either would deny a load that works.optionalis deliberately not folded intokind: collapsing an absent optional row toNonewould makedependencyIsPresenttrue for a package not on disk logos-basecamp#389 - Asked for by name at every call site, because widening a default moves callers silently: Basecamp’s three load paths (including two startup auto-loads in
main.cppthat bypassCoreModuleManagerentirely), logosctl’s five, and the standalone app’s logos-basecamp#399, logos-logoscore-cli#120, #121, logos-standalone-app#48, #51, #47 - A skip is reported, not just logged.
optional_skippedappears only when something was actually left out — a key that is always there says nothing — andlogosctl package depsmarks the edge on satisfied rows too, since optionality is a property of the edge, not a verdict on the package. An absent optional dependency had been rendering byte-identically to a required one that really is missing, which reads as “this install is broken” logos-liblogos#207, logos-logoscore-cli#119 - Proved end to end, in two modules directories.
test_optional_module_cppdeclares one optional dependency and nothing required; “absent” has to mean absent, because staging both and declining to load one leaves the dependency findable. The compile is itself an assertion —modules().test_basic_module_cppexists for a dependency declared only as optional, so a schema or umbrella regression fails the build logos-test-modules#57, logos-module-builder#246, logos-rust-sdk#54, logos-plugin-qt#37 - Two consumers switched over immediately:
eth_rpc_moduleretired itsglue.rsworkaround for both modules it was reaching untyped, keeping every call’s timeout bound (an absent provider costsPROBE_BUDGET, not the 20 s protocol deadline); anddelivery_modulemadeliblogos_rln_moduleoptional, which is why its e2e suite — a suite that never touches RLN — can load the module at all logos-evm-eth-rpc-module#11, logos-delivery-module#105
Windows — a GUI app, an installer, and a daemon that survives logout
- Three PEs were console-subsystem, measured rather than assumed.
WIN32_EXECUTABLEis also what gives the image an entry point (Qt6Core attaches itsWinMainunder exactly that property), so nothing extra had to be linked. Flipping only Basecamp would have made things worse, which is why the two spawn-site changes shipped with it; the children stay CUI deliberately, so running either from a terminal still prints. Countingconhost.execannot verify any of this —CREATE_NO_WINDOWallocates a console and only suppresses its window logos-basecamp#411, logos-container-subprocess#8, logos-view-module-runtime#32 - The log sink change was not incidental. A GUI-subsystem process started without a console has no stdio at all — the CRT leaves both streams at
_NO_CONSOLE_FILENO— so::dup(fileno(stdout))fails,start()returns false, and the app runs with no file logging whatsoever, silently, because the only channel that could report it is the one that is missing logos-basecamp#411 logosctl daemon start --detachhad two Windows bugs in one function.bInheritHandles=TRUEwith no handle list hands over every inheritable handle of the launching process, including the write end of a caller’s capture pipe — so$(logosctl daemon start --detach)and every ssh remote command hung until the daemon stopped. And an ssh session runs inside a job whose limit flags are0x2800(kill-on-close plus breakaway-ok, measured withQueryInformationJobObject), so the daemon died at logout; it now requestsCREATE_BREAKAWAY_FROM_JOBand falls back to starting inside the job when that is refused. The doc-test’s Windows leg now captures that start through a pipe, so a regression would hang the step rather than pass logos-logoscore-cli#134- Releases ship
logosctlalone, with Windows install steps verified as written on Windows 11 against the real 0.3.0-rc.2 zip — including the finding that Explorer’s zip handler copies the Zone.Identifier onto the extracted binaries whileExpand-Archivedoes not logos-logoscore-cli#133, logos-tutorial#90 - The module catalog publishes a
windows-x86_64variant. The action pin and the variant had to move together: the old pin already declared avariantsinput, so adding the value under it would have been accepted, expanded into the matrix, and scheduled against aninclude:with no entry — an emptyruns-onon every module release in the catalog logos-modules-release#55 - The EVM modules cross-build to
x86_64-windows, with CI on the keystore and token list proving the cross-build stays working;logos_host’s mingw dependencies (libwebsockets, libmicrohttpd) landed in the toolchain; Basecamp’s Windows CI stopped staging apkg/tree the next checkout could not delete; and storage-ui’s data directory path was fixed logos-evm-eth-rpc-module#13, logos-evm-fee-module#4, logos-evm-signer-ui#7, logos-evm-uniswap-module#9, logos-evm-keystore-module#12, logos-evm-token-list-module#7, logos-module-builder#241, #239, logos-basecamp#396, logos-storage-ui#87
Protocol — a reserved channel, a consented budget, and one ABI per closure
- The reservation. Two halves, because either alone leaves the other route open: a public
onEvent()that refuses a reserved name on all three transports (every consumer path bottoms out there, andonEventWhenAvailablerefuses early so a caller gets0rather than arming a subscription that would never fire), and wildcard fan-out that skips it — withheld at the host and at the receiving connection, since a wallet backend that fans deferred reads out and subscribes to events is exactly the case where the frame legitimately arrived for a named subscriber on the same connection logos-protocol#87 - And the marker it shipped without. #87 carried no MINOR bump and no feature macro, making it the third revision reporting MINOR 9 — so
LOGOS_PROTOCOL_HAS_RESERVED_EVENT_NAMESfollowed one commit later, opened before the relock wave so that what propagates is detectable logos-protocol#88 - The token exchange waited twice for one event, and the short wait gated the long one. capability_module asked “is the target there?” on its own 3250 ms budget while the very next statement asked the same question on the caller’s 20 s. From a real Basecamp log, the grant was discarded at 48.134 and the target answered at 48.180 — it had been up all along. The fix is ordering, not speed, and it is free: the handle is left in the consumer’s handle cache, so the acquire inside the following call is a hit. Measured why the module is late:
Module loaded:asserts only fork + exec + the token-pipe write, while the child’s QtRO registry binds lazily on its firstpublishObject— ~30 of those 33 ms are exec, dyld,QCoreApplicationand the stdin token read, before the plugin dylib is even opened logos-protocol#85 abi-closure-checkasserts what a version string cannot express: exactly one protocol-lib, exactly one qt-host, and the qt-host was built against the very protocol-lib the output links — two libraries both reading0.9.0can be different revisions with different layouts. It is recoverable from store paths alone, so the check needs no disassembly and is arch-independent, and every assertion is exercised in the failing direction against synthetic closures, since “no finding” as a pass verdict is the shape that goes vacuous unnoticed logos-protocol#86, #90- Two places where an override could split the pair. liblogos’ direct
logos-protocoledge stopped where an--override-inputstopped, leaving qt-sdk’s and plugin-qt’s copies behind — which is what aborted logos-rust-sdk’s doctests atfree(): invalid pointeronce the object grew. Anddefault-module-loaderwas declared with nofollowsat all, resolving its own protocol revision into the very process that loads it. Verified in the built closure, not the lock: the lock still holds nine distinct protocol revisions from node explosion in unrelated subtrees, and that number says nothing logos-liblogos#212, #214, logos-basecamp#401, logos-logoscore-cli#123, logos-standalone-app#49 - The relock wave carried the reserved channel through cpp-sdk, plugin-qt, qt-sdk, view-module-runtime, module-loader-qt, module-builder, capability-module, liblogos and on into Basecamp, logosctl and the standalone app logos-cpp-sdk#159, #161, logos-plugin-qt#35, #36, logos-qt-sdk#53, #55, logos-view-module-runtime#31, #33, logos-module-loader-qt#14, #15, logos-module-builder#232, #243, logos-capability-module#30, logos-liblogos#208, #210, logos-basecamp#413, logos-logoscore-cli#131, logos-standalone-app#52
EVM — a signer someone can read, and a wallet that can price a send
- A role is a set.
approver→approvers,custodian→custodians, so a terminal signer can approve alongsidesigner_uirather than displacing it. The properties that had to survive: an empty set admits nobody by the same arithmetic that made an empty name admit nobody; blanks and repeats are normalised out, sois_empty()and “admits nobody” can never disagree; and the pre-list spelling is refused as an unknown key, becausedeny_unknown_fieldsexists precisely so a typo cannot silently empty both roles logos-evm-keystore-module#8 - Two lists, structurally.
render_linesare the keystore’s words over the parsed intent;claim_linesare the requester’s and are worth nothing as evidence. The split is not a prefix an approver could drop or mis-match — and it is what letslogos-tx-decoderkeep its contract unchanged while the requester text it used to defend against leaves its input entirely. On the UI side that claim had been fetched and discarded outright, so a human was shown an account, a commitment hash and a digest and asked to approve it logos-evm-keystore-module#10, logos-evm-signer-ui#8, #12 - Token naming, hedged where it should be. Section 3 could say a call looks like
transfer(address,uint256)but not what the address is, and the raw integer was all a human got for the amount unless the ABI database happened to carry that contract’s decimals — which it does for exactly one of the 87 it names. The list’s answer is offered as “a NAME, not a check of the code”, with the list that answered on the line, because anyone who can add a custom token can put a friendly symbol on a hostile address; the amount is hedged twice over, since the argument position comes from a signature the decoder may have guessed. The e2e had been staging no token list at all, so the layer was only ever seen in its no-op shape logos-evm-signer-ui#14, #15 - The four wallet surfaces took the chain prefix their repos already carry —
evm_signer_ui,evm_keystore_ui,evm_signer_cli,evm_keystore_cli. Roles fail closed in both directions, so the keystore defaults and the renamed surfaces ship together in one catalog update logos-evm-signer-ui#9, #13, logos-evm-keystore-module#13, #14, logos-evm-signer-ui#11, logos-standalone-app#50 calltook a deadline of its own. It was the only wallet-facing method taking none, so a caller could only race this module’s timeout. The Ethereum wallet read under a 3 s grant against the 8 s socket timeout it had itself seeded into our sharedchains.json, gave up ~5 s before we would have, and rendered a RustDebugblob where our own sentence was about to arrive — a wrong endpoint and a slow one produced the identical screen. Reading the number harder is not the fix: a value read at T is raced by any sibling rewriting that shared file at T+1, and the published8describes a 16 s wall bound anyway logos-evm-eth-rpc-module#9, logos-evm-uniswap-module#8- No account under ~0.27 ETH could be priced through the verified proxy. Given a tx with a
fromand nogas, the proxy applies it at the full 60,000,000 block gas limit priced at ~20× base fee and runs the balance check before estimating; geth instead caps the gas search atbalance / feeCapand answers0x5208. Sending a zero fee cap oneth_estimateGasis what makes the small-balance case estimable logos-evm-fee-module#5
Delivery and RLN — an agnostic plugin, and a demo that shows it working
- The module owns the LEZ knowledge the library dropped. Four agnostic callbacks, none carrying a registry or an identifier;
configureRLN(cfgJson)installs the plugin, enables the in-process bridge and startsliblogos_rln_module, and each trampoline adds the configured registry id and rln identifier on the way through, because the library sends neither.configureRLNrefuses to run aftercreateNoderather than reconfiguring — the library reads the plugin at node creation, so a later call would silently do nothing logos-delivery-module#94, #100 - Then it deadlocked itself. Every lp call from a worker thread marshals onto the Qt main thread with a
BlockingQueuedConnection, so a module method that blocks that thread waiting for a lane can never be answered; lifecycle is served inline on the calling thread instead. The alternatives were each worse in a stated way — pre-warming the typed client fixes only the first call, and fire-and-forget would drop the synchronous start-failure reporting logos-delivery-module#105, #107, #109, #111, #110 - The demo drives it end to end — membership re-read on push and every 10 s, epoch quota every 2 s and on every proof, the epoch index decoded from each proof’s external nullifier with a locally ticked countdown, and proof/validation counts off the module’s own dispatch events. It also found that
epochSizeSec, documented and implemented as optional, is not:start()rejects a config without it, so leaving the field blank failedconfigureRlnoutright. Sender anonymity level is now acreateNodedropdown logos-delivery-demo#27, #26, logos-delivery-module#98 - Coverage went wider: delivery, chat and the demo build on aarch64-linux — free GitHub-hosted arm64 runners, no self-hosted infrastructure — chat-ui builds on all three platforms, the module API reference publishes to GitHub Pages, and a page now says plainly how
logos.devandlogos.testdiffer logos-delivery-module#103, logos-chat-module#69, logos-chat-ui#60, logos-delivery-demo#28, logos-delivery-module#95, #102
App-to-app intents — the shell’s namespace, and what may join its surface
Last week the round trip closed and signer_ui became its first cross-app consumer. This week the surface itself was given a boundary and written down.
- Two reserved namespaces, enforced in two different places, which is the same split the rest of the design turns on.
logos.*is the platform’s — liblogos, logoscore, anything below the shell — and is refused by the frozen surface, because it outlives any particular shell.basecamp.*is this shell’s and is refused inIntentRegistry, because which shell owns which prefix is policy. Basecamp is one frontend among several possible ones, and a capability of this shell has no business claiming the name of the platform every frontend sits on. Nothing claimslogos.*yet — it is reserved now because reserving it later, once apps have declaredusesagainst it, is not possible. Names match byte-exactly, no case folding and no Unicode normalisation, because a name is a contract between independently shipped apps and “looks the same” is not good enough logos-basecamp#390 - Seven shell providers, registered in code because the shell has no
metadata.json, in two groups whose dividing line is load-bearing. Navigation —basecamp.repositories.manage,basecamp.settings.open,basecamp.apps.open,basecamp.apps.launch— are all hand-offs, sinceokmeans “you are there”, not “we are done”, and returning the user would undo the request. The threebasecamp.packages.confirm_*dialogs are not. The navigation list must stay navigation-only, and that is a security property: the broker skips the chooser when the shell is the sole provider, so anything in that group runs with no consent dialog at all. That is right for moving between the shell’s own sections — nothing crosses a boundary, and confirming a navigation the user just asked for is a dialog answering itself — and stops being right the instant an entry mutates state logos-basecamp#390 basecamp.apps.launchtakes the app name as a parameter and answers the same way whether or not the app exists. Both halves are deliberate. A parameter rather thanbasecamp.launch.<appName>, because app names are not bound by the intent-name grammar (anything with a hyphen or a capital would be silently dropped),provideswould have to be re-registered on every install, and a caller’suseswould have to name each app it might launch — which is exactly the “a request names a capability, never a provider” line. And a constant answer, because “launch X, tell me if it worked” is otherwise an installed-app enumeration oracle: iterate plausible names, read the answers, recover the user’s whole app list. A malformed payload takes the same path, sincebad_requestwould confirm the name was well-formed but absent, which is half the oracle back logos-basecamp#390- Two dialogs are restricted, one is not. Attribution is enough when the user has context to judge against — they clicked something, and “Chat App wants to send funds” is a question they can answer. An unsolicited prompt to remove or downgrade one of your packages has no such context, and its correct answer is always no; a dialog whose right answer is unconditional can only cost you, because it trains dismissal and one mis-click is destructive and not undoable. So
confirm_uninstallandconfirm_upgradeare restricted topackage_manager_ui, whileconfirm_installstays open — an app saying “you need X” is legitimate logos-basecamp#390 - No compatibility window for the old
logos.*spellings.package_manager_uiis the only consumer and was never released declaring them, so no installed copy asks for them — which makes an alias pure cost: restrictions are keyed on the name as submitted and checked before delivery, so a survivinglogos.packages.confirm_uninstallwould be a second live path to a restricted, destructive capability that nothing on disk would ever have used logos-basecamp#390 - PMUI became a provider as well as a requester. It declares
packages.installas a hand-off, so an install raised from another app lands in the same reveal-and-confirm flow it runs internally rather than a second one built beside it logos-package-manager-ui#78, #79
Catalogs, downloads and repository identity
- Same-named repositories, told apart end to end. A fork of the official catalog in the wild publishes the same display name, which left the two indistinguishable in the app manager and in Settings and made an install from one appear to be running in the other. Source information was added where it originates — the downloader — carried across the module ABI, and then surfaced in both frontends. The label is applied only on collision (“Logos Official (logos-co)” vs “(0x-r4bbit)”), derived once in
PackageCoordinatorso no two views can disagree about what a repository is called, with a Settings warning when a configured repository claims a name another already uses, and in-flight install state — stage, progress, errors — scoped to the repository the operation targets, so only the row acted on shows it logos-package-downloader#38, logos-package-downloader-module#37, #38, logos-package-manager-ui#81, logos-basecamp#414 - A 143 MB download failed instantly with nothing in the log — because a complete artifact from an earlier run sat at the predictable
/tmp/<pkg>-<version>.lgx, owned by another account. The temp root is sticky, so the loser can neither overwrite nor unlink it, and any user can squat that path to deny a package to everyone else on the host. Staging moved to a per-uid0700directory, checked withlstatso a planted symlink is not followed, alongside three reporting gaps where an unreachable catalog and one that simply lacks the package produced the same verdict logos-package-downloader#37, logos-package-downloader-module#35, logos-basecamp#400, logos-logoscore-cli#122 - The install that “failed at step download” had completed the download. The daemon’s own hops into
package_manager/package_downloaderran on the 20 s transport default, whiledownloadResolvedDependenciesfetches a whole archive inside one call andinspectPackagegunzips it into memory twice. The module kept downloading after the daemon gave up, and the pre-staging downloader truncated the same path on every retry — which is why the reporter read it as “truncated at different sizes”. The file was complete, at exactly the catalog’s recorded byte count logos-logoscore-cli#124, logos-package-downloader-module#36, logos-basecamp#412, logos-logoscore-cli#129 logosctl callsent addresses as numbers.std::stodaccepts hex-float literals, so0xf39Fd6…was consumed whole and dispatched as a JSON number; only decimal notation may become one now, andinf/nan— which JSON cannot carry and which were serialising asnull— stay strings logos-logoscore-cli#127
Basecamp — the welcome page, responsiveness, logging and the app-manager test layer
- Clicking an app tile froze the UI, and the freeze landed before the spinner rather than under it. Two independent causes: the dependency loop ran on the GUI thread, each iteration a blocking load that spawns a subprocess and waits (~35 ms warm, ~150 ms cold), now on a dedicated worker with the ordering asserted rather than assumed — an off-thread load posts its capability-module registration to the owner thread as it goes, and the completion callback after all of them. And the
singleShot(0)meant to paint the spinner first never did: measured on Qt 6.9.2, 0 frames at 0 ms and at 1 ms, nested or not logos-basecamp#410 - Session logging moved onto the mechanism logosctl’s daemon uses, configured by an optional
config.yamlin the session directory with the same keys, spellings and defaults. Rotation, the size cap and retention come from spdlog’s rotating sink, retention prunes across launches rather than within one, and it works on Windows — where the old redirector was a no-op returningfalselogos-basecamp#402 - The welcome page stopped being a placeholder and became the way into the app. Search over apps and packages with a type filter,
Ctrl+Kto focus it, and results that open, install, or hand off to the full list rather than dead-ending; a recently-closed row backed by a store that persists across launches instead of living only in the session; and aShortcutBridgemapping QML objects to their owning pane, because a workspace ofQQuickWidgetdocks otherwise swallows window-level shortcuts. It then became a proper tab in the workspace tab bar, with a spacer tab kept out of view and welcome visibility synced against the open docks rather than inferred at each call site logos-basecamp#388, #391 - An app’s module dependency needing an upgrade offered reinstall instead of update — the wrong verb on the one flow where the distinction decides whether the user keeps their data logos-basecamp#393
- A clear button in the design system’s searchbar, which is what the welcome page and PMUI search both sit on logos-design-system#55
- The MCP-driven UI test layer took on the app manager and the dashboard — search narrowing, no-match, special characters, category filtering, reload settling without losing apps, context menus matching install state, Details opening the Add Application dialog, dialog wording for an installed app, the apps inspector’s search, and the dashboard’s version/build-type/commit display logos-basecamp#368, #369, #370, #371, #372, #373, #374, #375, #386, #387
- Release pins aligned with logosctl at master, CI timeouts raised and S3 upload retries bumped logos-basecamp#403, logos-logoscore-cli#126, logos-basecamp#407, #408
Blockchain — fees replace explicit account registration
register_public_accountand theinitializepath are gone in favour of fees: an account is claimed by funding it.payeris now an explicit FFI argument, which also unblocked program deployment, and the explorer shows fee declarations logos-execution-zone-module#55, #56, logos-execution-zone-wallet-ui#47, #48, lez-explorer-ui#22, #23- Blend info surfaced in the blockchain UI logos-blockchain-ui#64
Authoring, releases and hygiene
- Rust module templates, both rust-first: a minimal one and one wrapping a real external library (zlib via
extern "C"), so the swap points are commented rather than stubbed.nix flake init -tcovered four C++ shapes and no Rust one, so every Rust module in the org started as a hand-copy of a sibling — which is how the committed-lock and trait-naming traps kept being rediscovered logos-module-builder#237 - A rust-first module’s
about_to_unloadnever reached the author’s impl, and failed silently rather than loudly: the export exists, the Qt glue always declaresQ_INVOKABLE int aboutToUnload(), so the host’s by-name lookup succeeds, reads0= Synchronous, and tears the module down without waiting. All 16 Rust modules in the wild are rust-first. Autoref specialization — the trickanyhowuses to tellDisplayfromDebug— resolves at a concrete call site what stable Rust cannot detect at a generic one; contract-first output is byte-identical logos-rust-sdk#60 ipc-testhad been red since #52, and it was not a test failure —undefined symbol: lp_client_set_subscription_status_cb, becausetests/flake.nixpinned logoscore-cli at a June revision in the URL. Un-pinning alone does not fix it: the modules compile against module-builder’s protocol and load against logoscore-cli’s, so the fix is afollowslogos-rust-sdk#55, #56, #57, #58, #59- The resolved metadata now reaches the artifact. A platform-keyed field used to be resolved for the build and not for the shipped
metadata.json, so the loader, lgpm and the.lgxmanifest read the base list while the build used the resolved one. Staging it in the source rather than apreConfigurehook is the part that had to be measured: plugin-qt splices that hook after the generator has already read./metadata.json, so a Qt-path module linked the overlay’s dependency and took its umbrella members from the base list. That re-admitsdependencies/optional_dependenciesas overlay keys, with a duplicate across base and overlay refused by name logos-module-builder#231 - The header-copy dependency path is gone, having already been retired at the other end — it existed only to carry names to a place that rejects them. Measured before removing anything: of 104
metadata.jsonin the workspace, 31 publish no LIDL and not one is named in any module’sdependencies. 72 insertions, 160 deletions logos-module-builder#230 - Four new executable tutorials covering everything since
tutorial-v4— Rust, concurrent dispatch, optional dependencies withmodules_state, and caller identity, of which the last two had essentially nothing in the repo. They measure rather than assert: Concurrent Dispatch fires four calls and asserts peak overlap 4, flips one metadata key to"single", re-runs the identical fan-out and asserts peak 1 logos-tutorial#89, #87, #88 - The four Monero wallet repos joined the doctest hub, their tutorial names derived from the resolved specs rather than typed; the backend is deliberately unlisted, since it has no spec of its own and would link to a report that will never be published logos-doctest-hub#10
- A flaky concurrency test was the measurement, not the budget.
elapsed < 2 × 1000 mstimes the machine as well as the lock: on a busy host it read 4233 ms and failed even though the daemon log shows the two loads completing 22 ms apart. Widening the constant only moves the threshold, so the assertion now reads overlap from the hosts’ own marks logos-liblogos#206 - Two local CMake build trees were swept into git and untracked again — 75 files and 5.3 MB in the workspace, which was 46% of the flake source copied into the store on every eval, with
.gitignorerules added so the nextgit add -Acannot repeat it logos-workspace#105, logos-protocol#89 - Release and module hygiene: the module catalog moved through 0.2.5-rc1 and rc2 and gained
logos-json-rpc-bridge; storage reports total bytes in download progress and dropped unused doctests; test-modules went green on macOS and got the qml-modules check building; the module viewer includeslogos_core.hinstead of redeclaring its prototypes; and the crates.io fetch wiring and logos-nix native overlays were finished across the builder and both SDKs logos-modules-release#54, #56, #58, #59, logos-storage-module#85, #87, logos-storage-ui#89, logos-test-modules#58, #59, #60, #61, logos-module-viewer#9, logos-module-builder#233, #234, #236, #240, #242, #244, #245, logos-qt-sdk#54, logos-evm-railgun-module#6, logos-evm-fee-module#3, logos-evm-token-list-module#6, logos-evm-uniswap-module#7, logos-evm-keystore-module#11, logos-evm-signer-ui#6, #10, logos-evm-eth-rpc-module#10, logos-logoscore-cli#125, #130, #132, logos-liblogos#213, logos-cpp-sdk#155, #156, #157
Appendix: all merged PRs, by repo
lez-explorer-ui, logos-basecamp, logos-blockchain-ui, logos-capability-module, logos-chat-module, logos-chat-ui, logos-container-subprocess, logos-cpp-sdk, logos-delivery-demo, logos-delivery-module, logos-design-system, logos-doctest-hub, logos-evm-eth-rpc-module, logos-evm-fee-module, logos-evm-keystore-module, logos-evm-railgun-module, logos-evm-signer-ui, logos-evm-token-list-module, logos-evm-uniswap-module, logos-execution-zone-module, logos-execution-zone-wallet-ui, logos-liblogos, logos-lidl, logos-logoscore-cli, logos-module, logos-module-builder, logos-module-loader-qt, logos-module-viewer, logos-modules-release, logos-package, logos-package-downloader, logos-package-downloader-module, logos-package-manager, logos-package-manager-module, logos-package-manager-ui, logos-plugin-qt, logos-protocol, logos-qt-sdk, logos-rust-sdk, logos-standalone-app, logos-storage-module, logos-storage-ui, logos-test-modules, logos-tutorial, logos-view-module-runtime, logos-workspace, nix-bundle-lgx, nix-bundle-logos-module-install
lez-explorer-ui
logos-basecamp
- feat: tell same-named repositories apart in the UI (#414)
- chore: relock the wave onto the reserved completion channel (#413)
- chore(deps): pick up the catalog fix that dropped whole repositories (#412)
- Windows: a GUI-subsystem app, and an installer with a portable mode (#411)
- fix(apps): load an app’s core dependencies off the GUI thread (#410)
- feat(plugins): load a ui plugin’s own optional dependencies (#409)
- fix(ci): bump retries count when uploading to s3 bucket (#408)
- ci: bump timeouts (#407)
- chore(deps): align release pins with logosctl at latest master (#403)
- feat(logging): configure session logging from a YAML document (#402)
- fix(nix): pair qt-host with the protocol the app stages, and check it in CI (#401)
- chore(deps): bump package_downloader for the private download staging (#400)
- feat: load optional dependencies by default (#399)
- ci(windows): stop staging a pkg/ tree the next checkout cannot delete (#396)
- fix: when a module dependency for app needs to be upgraded, it showed… (#393)
- feat: update the welcome page tab (#391)
- feat: update basecamp intents and expose more intents for basecamp (#390)
- fix(deps): an optional dependency must not block a load (#389)
- feat: update the welcome page (#388)
- Test/MCP UI apps inspector search filters the table (#387)
- Test/MCP UI dashboard shows version, build type and commits (#386)
- Test/MCP UI app manager dialog wording for an installed app (#375)
- Test/MCP UI app manager Details opens the Add Application dialog (#374)
- Test/MCP UI app manager context menu matches install state (#373)
- Test/MCP UI app manager reload settles without losing apps (#372)
- Test/MCP UI app manager category selection filters the grid (#371)
- Test/MCP UI app manager search tolerates special characters (#370)
- Test/MCP UI app manager search with no match hides every section (#369)
- Test/MCP UI app manager search narrows the grid (#368)
logos-blockchain-ui
logos-capability-module
logos-chat-module
logos-chat-ui
logos-container-subprocess
logos-cpp-sdk
- chore: relock logos-protocol onto the reserved completion channel (#161)
- chore: repin logos-protocol onto 4638634 (#159)
- feat(host): take LogosLoadDeps, and complete the optional-dependency surface (#158)
- feat(codegen): carry optional_dependencies through the contract (#157)
- refactor(codegen): take the umbrella’s members from the —dep flags (#156)
- feat(codegen): give optional dependencies the same umbrella member (#155)
logos-delivery-demo
- ci: build the demo on aarch64-linux (#28)
- feat: configure RLN in the demo, and show it live (#27)
- feat: pick the sender anonymity level in createNode (#26)
logos-delivery-module
- chore(nix): relock onto the rln-modules lez repoint (#111)
- fix(doctests): install the RLN dependency chain the module now needs (#110)
- chore(nix): point liblogos_rln_module at feat/lip-alignment (#109)
- chore(nix): bump liblogos_rln_module onto the dependency-chain fix (#107)
- fix(rln): serve backend lifecycle inline so configureRln cannot deadlock (#105)
- ci: build and test on aarch64-linux (#103)
- docs: add a page comparing logos.dev and logos.test (#102)
- feat(rln): own the LEZ knowledge the delivery plugin no longer carries (#100)
- chore: bump logos-delivery for the anonymityLevel messaging option (#98)
- docs: publish the module API reference to GitHub Pages (#95)
- Make RLN API module pluggable with logos-delivery (#94)
logos-design-system
logos-doctest-hub
logos-evm-eth-rpc-module
- feat: cross-build to x86_64-windows (#13)
- docs: optional is about absence, not the closure (#12)
- feat: declare verified_proxy_module and modules_state optional dependencies (#11)
- chore(deps): bump logos-module-builder to 9f420c290 (crates.io fetch fix) (#10)
- feat: let a caller bound
callwith its own deadline (#9)
logos-evm-fee-module
- fix: send a zero fee cap on eth_estimateGas so small accounts can be estimated (#5)
- feat: cross-build to x86_64-windows (#4)
- chore(deps): bump logos-module-builder to 9f420c29 (#3)
logos-evm-keystore-module
- docs: the CLI is refused at Tier D; the offer TTL is 60 s (#14)
- feat!: default roles are evm_signer_ui / evm_keystore_ui (#13)
- ci: prove the Windows cross-build stays working (#12)
- chore(deps): bump logos-module-builder to 9f420c290 (#11)
- feat(keystore): hand the approver the requester’s claim as its own list (#10)
- fix(keystore): split the ack timer into requester cleanup and abandoned-offer GC (#9)
- feat(keystore): a role is a set, so a second holder can be added (#8)
logos-evm-railgun-module
logos-evm-signer-ui
- test(e2e): let the host find the optional dependency, as a user would (#15)
- feat(tokens): say what a token list calls the address being signed to (#14)
- Give evm_signer_ui a display_name (#13)
- feat: three sections — who asked, what is signed, what this signer makes of it (#12)
- docs: configure takes approvers/custodians lists, not singular keys (#11)
- ci: the view probe installs Nix through setup-nix-cache-action (#10)
- feat!: rename the module to evm_signer_ui (#9)
- fix: show the requester’s claimed purpose at approval (#8)
- feat: cross-build to x86_64-windows (#7)
- chore(deps): bump logos-module-builder to 9f420c290 (#6)
logos-evm-token-list-module
- ci: prove the Windows cross-build stays working (#7)
- chore(deps): bump logos-module-builder for the crates.io fetch fix (#6)
logos-evm-uniswap-module
- feat: cross-build to x86_64-windows (#9)
- fix: pass eth_rpc’s new deadline argument (#8)
- chore(deps): bump logos-module-builder to 9f420c290 (#7)
logos-execution-zone-module
- feat:
payeris now available in FFI as an argument (#56) - feat: rm
register_public_accountin favor of fees (#55)
logos-execution-zone-wallet-ui
logos-liblogos
- fix: give the default module loader our logos-protocol, and relock the wave (#214)
- chore(nix): relock default-container for the Windows console fix (#213)
- fix(nix): one logos-protocol, and one qt-host, under an override (#212)
- fix: the doctest, and the docs, still passed a bool to logos_core_load_module (#211)
- chore: relock onto protocol 0.9 via the repinned upstreams (#210)
- chore(deps): take logos-cpp-sdk from 2026-08-24 up to master (#208)
- feat(core): best-effort loading of optional dependencies (#207)
- test(core): read load overlap from the hosts’ marks, not a stopwatch (#206)
- feat(core): load semantics for optional dependencies (#205)
logos-lidl
logos-logoscore-cli
- fix(daemon): Windows —detach stops holding the caller’s pipes and outlives an ssh session (#134)
- ci(release): publish only logosctl, and add Windows install steps (#133)
- docs(spec): reserved event names are never delivered by watch (#132)
- chore: relock the wave onto the reserved completion channel (#131)
- chore(nix): relock liblogos so the daemon stops popping consoles (#130)
- chore(deps): pick up the catalog fix that dropped whole repositories (#129)
- fix(call): only decimal notation is a number, so a 0x address stays a string (#127)
- chore(deps): align release pins with Basecamp at latest master (#126)
- test(output): force human mode instead of relying on isatty(stdout) (#125)
- fix(package): give the daemon’s package-module calls a transfer deadline, and say why a call failed (#124)
- fix(nix): pair qt-host with the protocol we bundle, and check it in CI (#123)
- chore(deps): bump package_downloader for the private download staging (#122)
- feat: load optional dependencies by default, and report the skips (#121)
- chore(core): take LogosLoadDeps in place of the with_dependencies bool (#120)
- feat(deps): take the optional-dependency projection, and surface it in
package deps(#119)
logos-module
logos-module-builder
- test: build a universal module with optional_dependencies (#246)
- chore(deps): relock logos-plugin-qt — writer and reader back on one LIDL grammar (#245)
- chore(deps): relock logos-rust-sdk — the rust-first teardown hook (#244)
- chore: relock the wave onto the reserved completion channel (#243)
- fix(deps): one nix-bundle-lgx for both bundler paths (#242)
- chore(deps): relock logos-nix (mingw libwebsockets) (#241)
- chore(deps): relock logos-rust-sdk for interface descriptions (#240)
- chore(deps): relock logos-nix (mingw libmicrohttpd, Nim 2.2.10) (#239)
- feat(templates): Rust module templates — minimal, and one wrapping a C library (#237)
- chore(deps): relock logos-rust-sdk onto the keyword-escape fix (#236)
- docs: load-module example takes LogosLoadDeps, not a bool (#235)
- fix(nix): finish the crates.io wiring — relock, and follow our logos-nix (#234)
- fix(nix): apply logos-nix’s native overlays in mkPkgsWith (#233)
- chore: relock logos-protocol onto the readiness-gated token exchange (#232)
- feat(platforms): ship the resolved metadata, and allow platform-keyed deps (#231)
- refactor(deps): retire the header-copy path, which was already dead (#230)
- chore(deps): take nix-bundle-lgx with the new manifest keys (#229)
- feat(metadata): optional_dependencies, a third dependency kind (#228)
logos-module-loader-qt
- chore: relock protocol, cpp-sdk and qt-sdk onto the reserved completion channel (#15)
- chore: repin logos-protocol, logos-cpp-sdk and logos-qt-sdk (#14)
logos-module-viewer
logos-modules-release
- feat: add logos-json-rpc-bridge (#59)
- chore: bump modules for 0.2.5-rc2 (#58)
- chore: bump modules for 0.2.5-rc2 (#56)
- release: publish the windows-x86_64 variant (#55)
- chore: bump modules for
0.2.5-rc1(#54)
logos-package
logos-package-downloader
- fix(catalog): stop one array-valued manifest field from dropping a whole repo (#39)
- feat: add repo source information to differentiate repos which use same name (#38)
- fix(download): stage in a private dir, and say why a download failed (#37)
logos-package-downloader-module
- feat: distinguish between same repo names but from different users in… (#38)
- feat: add repo source information to differentiate repos which use sa… (#37)
- chore(deps): pick up the catalog fix that dropped whole repositories (#36)
- chore(deps): bump logos-package-downloader for the private download staging (#35)
logos-package-manager
logos-package-manager-module
- chore(deps): bump logos-module-builder onto master (#69)
- feat(deps): project optional_dependencies across the module ABI (#68)
- feat(deps): carry the optional flag through to the dependency projection (#67)
logos-package-manager-ui
- feat: distinguish between same repo names but from different users in… (#81)
- fix: basecamp intents (#79)
- feat: add an intent to install module which launches the same flow to install from module as it was from pmui (#78)
logos-plugin-qt
- chore(deps): relock logos-lidl onto optional_depends (#37)
- chore: relock logos-protocol onto the reserved completion channel (#36)
- chore: repin logos-protocol onto 4638634 (#35)
logos-protocol
- ci: run abi-closure-check-tests, and make it executable in the Linux sandbox (#90)
- chore: untrack the local CMake build tree swept into fdc09ff (#89)
- feat: a feature macro for the reserved completion channel (#88)
- feat: reserve the completion channel so only a transport can read it (#87)
- feat: publish the qt-host/protocol pairing rule as a check (#86)
- fix: the token exchange waits on the caller’s budget, not its own (#85)
logos-qt-sdk
- chore: relock protocol, cpp-sdk and plugin-qt onto the reserved completion channel (#55)
- fix(nix): apply logos-nix’s native overlays, instead of a second Qt (#54)
- chore: repin logos-protocol, logos-cpp-sdk and logos-plugin-qt (#53)
- feat(host): forward LogosLoadDeps and the optional-load report (#51)
logos-rust-sdk
- feat(unload): let a rust-first module opt into about_to_unload (#60)
- fix(lidl-gen): carry a method’s doc comment into the interface listing (#59)
- test(doctests): dump the daemon’s log, which is where the reason lives (#58)
- fix(lidl-gen): escape contract names that are Rust keywords (#57)
- fix(nix): apply logos-nix’s native overlays to both package sets (#56)
- fix(tests): make ipc-test green — four layers of protocol drift (#55)
- fix(lidl-gen): carry optional_depends, which serde was dropping (#54)
logos-standalone-app
- chore: relock the wave onto the reserved completion channel (#52)
- feat(plugins): load the plugin’s own optional dependencies (#51)
- docs: comments name evm_signer_ui (#50)
- fix(nix): pair qt-host with the protocol we ship, and check it in CI (#49)
- feat: load optional dependencies alongside required ones (#48)
- refactor(core): reach the core through the SDK wrapper, not our own prototypes (#47)
logos-storage-module
logos-storage-ui
logos-test-modules
- fix(thread-safety): generated dummies load under their own names (#61)
- fix: test-modules green on macOS — one runtime pin set, re-signed test plugins (#60)
- test(unload): cover the teardown contract for a rust-first module (#59)
- fix: make the qml-modules check build and pass (#58)
- test(optional): prove a module loads without its optional dependency (#57)
logos-tutorial
- docs: list the Windows installer among the distribution outputs (#90)
- Tutorials for everything since tutorial-v4 (#89)
- docs(guide): the two Rust scaffolds (#88)
- docs(guide): the three dependency kinds, and when to reach for optional (#87)
logos-view-module-runtime
- fix: make cpp-sdk’s logos-protocol follow ours, and relock the wave (#33)
- fix(windows): spawn ui-host with CREATE_NO_WINDOW (#32)
- chore: relock logos-protocol and logos-plugin-qt onto protocol 0.9 (#31)