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) and interface_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, .lgx bundler, 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 as missing, 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 frozen LogosIntent.h; basecamp.* is this shell’s and enforced in IntentRegistry — 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.launch takes 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_uninstall and confirm_upgrade are restricted to package_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 old logos.* spellings: restrictions are keyed on the name as submitted, so a surviving logos.packages.confirm_uninstall would 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 PackageCoordinator so 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 --event is the reachable form. logos-evm-keystore-cli’s README already documented the consequence, as a platform property rather than a bug: a create_mnemonic reply is a recovery phrase. Reserved now on all three transports, at onEvent() 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.exe links as a GUI-subsystem PE, and the two child hosts spawn with CREATE_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 .zip is 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-host bakes sizeof(LogosAPIClient) into its own operator new; logos-protocol defines that constructor — and #85 took the object 88 → 96 bytes. Splicing operator 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) and malloc(): corrupted top size on glibc, attributed to an unrelated later allocation. abi-closure-check ships 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 returned expired_no_ack for 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. provides is an array of intent objects; value(key, "") calls get<std::string>() on it and throws, and the try wrapped the entire package loop — so one package carrying intents discarded every package in that repository. The official catalog has no provides on 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+K to 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 a ShortcutBridge that routes shortcuts to the focused QML pane, since a workspace of QQuickWidget docks 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 createNode logos-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_depends joins 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 into depends would have been worse than dropping them: lidl_gen_client writes depends back out as dependencies, 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 published packages.<sys>.lidl, no dep build — and never collectAllModuleDeps. That is precisely the arrangement eth_rpc_module had been hand-rolling, with dependencies: [], 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_OPTIONAL brings up whichever optional dependencies are installed, ordered ahead of the module that names them. The bestEffort set 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 name missing, so app -opt-> extra -req-> ghost failed the load of app outright logos-liblogos#207
  • The bool became 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 old false/true makes 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.json is the only copy an installer or catalog can read before the package is unpacked, so both new kinds reach it — optional_dependencies copied through, interface_dependencies transformed 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. Manifest CURRENT_VERSION 0.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.optional is per-edge, with two conservative propagation rules: a required child of an optional edge inherits the mark, and flatten()’s dedup collapses with AND, so reachable by any required edge means required. A NotInstalled node 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. toLogosMap copied dependencies and dropped optionalDependencies, 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: splitDependencyRows counted every not_installed row as a blocker, so the user got a popup naming a dependency nothing requires. The exemption is not special-cased to not_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. optional is deliberately not folded into kind: collapsing an absent optional row to None would make dependencyIsPresent true 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.cpp that bypass CoreModuleManager entirely), 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_skipped appears only when something was actually left out — a key that is always there says nothing — and logosctl package deps marks 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_cpp declares 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_cpp exists 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_module retired its glue.rs workaround for both modules it was reaching untyped, keeping every call’s timeout bound (an absent provider costs PROBE_BUDGET, not the 20 s protocol deadline); and delivery_module made liblogos_rln_module optional, 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_EXECUTABLE is also what gives the image an entry point (Qt6Core attaches its WinMain under 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. Counting conhost.exe cannot verify any of this — CREATE_NO_WINDOW allocates 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 --detach had two Windows bugs in one function. bInheritHandles=TRUE with 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 are 0x2800 (kill-on-close plus breakaway-ok, measured with QueryInformationJobObject), so the daemon died at logout; it now requests CREATE_BREAKAWAY_FROM_JOB and 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 logosctl alone, 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 while Expand-Archive does not logos-logoscore-cli#133, logos-tutorial#90
  • The module catalog publishes a windows-x86_64 variant. The action pin and the variant had to move together: the old pin already declared a variants input, so adding the value under it would have been accepted, expanded into the matrix, and scheduled against an include: with no entry — an empty runs-on on 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 a pkg/ 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, and onEventWhenAvailable refuses early so a caller gets 0 rather 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_NAMES followed 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 first publishObject — ~30 of those 33 ms are exec, dyld, QCoreApplication and the stdin token read, before the plugin dylib is even opened logos-protocol#85
  • abi-closure-check asserts 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 reading 0.9.0 can 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-protocol edge stopped where an --override-input stopped, leaving qt-sdk’s and plugin-qt’s copies behind — which is what aborted logos-rust-sdk’s doctests at free(): invalid pointer once the object grew. And default-module-loader was declared with no follows at 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. approverapprovers, custodiancustodians, so a terminal signer can approve alongside signer_ui rather 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, so is_empty() and “admits nobody” can never disagree; and the pre-list spelling is refused as an unknown key, because deny_unknown_fields exists precisely so a typo cannot silently empty both roles logos-evm-keystore-module#8
  • Two lists, structurally. render_lines are the keystore’s words over the parsed intent; claim_lines are 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 lets logos-tx-decoder keep 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 carryevm_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
  • call took 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 shared chains.json, gave up ~5 s before we would have, and rendered a Rust Debug blob 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 published 8 describes 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 from and no gas, 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 at balance / feeCap and answers 0x5208. Sending a zero fee cap on eth_estimateGas is 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 starts liblogos_rln_module, and each trampoline adds the configured registry id and rln identifier on the way through, because the library sends neither. configureRLN refuses to run after createNode rather 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 failed configureRln outright. Sender anonymity level is now a createNode dropdown 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.dev and logos.test differ 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 in IntentRegistry, 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 claims logos.* yet — it is reserved now because reserving it later, once apps have declared uses against 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, since ok means “you are there”, not “we are done”, and returning the user would undo the request. The three basecamp.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.launch takes the app name as a parameter and answers the same way whether or not the app exists. Both halves are deliberate. A parameter rather than basecamp.launch.<appName>, because app names are not bound by the intent-name grammar (anything with a hyphen or a capital would be silently dropped), provides would have to be re-registered on every install, and a caller’s uses would 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, since bad_request would 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_uninstall and confirm_upgrade are restricted to package_manager_ui, while confirm_install stays open — an app saying “you need X” is legitimate logos-basecamp#390
  • No compatibility window for the old logos.* spellings. package_manager_ui is 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 surviving logos.packages.confirm_uninstall would 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.install as 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 PackageCoordinator so 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-uid 0700 directory, checked with lstat so 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_downloader ran on the 20 s transport default, while downloadResolvedDependencies fetches a whole archive inside one call and inspectPackage gunzips 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 call sent addresses as numbers. std::stod accepts hex-float literals, so 0xf39Fd6… was consumed whole and dispatched as a JSON number; only decimal notation may become one now, and inf/nan — which JSON cannot carry and which were serialising as null — 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.yaml in 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 returning false logos-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+K to 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 a ShortcutBridge mapping QML objects to their owning pane, because a workspace of QQuickWidget docks 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

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 -t covered 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_unload never reached the author’s impl, and failed silently rather than loudly: the export exists, the Qt glue always declares Q_INVOKABLE int aboutToUnload(), so the host’s by-name lookup succeeds, reads 0 = Synchronous, and tears the module down without waiting. All 16 Rust modules in the wild are rust-first. Autoref specialization — the trick anyhow uses to tell Display from Debug — 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-test had been red since #52, and it was not a test failureundefined symbol: lp_client_set_subscription_status_cb, because tests/flake.nix pinned 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 a follows logos-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 .lgx manifest read the base list while the build used the resolved one. Staging it in the source rather than a preConfigure hook 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-admits dependencies / optional_dependencies as 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.json in the workspace, 31 publish no LIDL and not one is named in any module’s dependencies. 72 insertions, 160 deletions logos-module-builder#230
  • Four new executable tutorials covering everything since tutorial-v4 — Rust, concurrent dispatch, optional dependencies with modules_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 ms times 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 .gitignore rules added so the next git add -A cannot 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 includes logos_core.h instead 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

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