Logoscore Weekly Update — 2026-09-07
Highlights
- App-to-app intents completed the round trip, and got their first cross-app consumer. An intent raised by one app is now routed back to that caller once the provider has finished, and
signer_uideclaresevm.signing.approve— soIntentBroker::activateloads the approver and brings it forward with the request on screen, closing the gap that repo’s own spec recorded as “no autoload”: with the plugin closed, a signature request waited and nothing showed it. The intent carries one keystore handle and nothing renderable — a doorbell, not a courier — which is what keepsrenderLinesthe keystore’s own words and the requester attribution real logos-basecamp#378, logos-evm-signer-ui#5 - Basecamp can now be run against fixtures instead of a live stack. Mock backends and a mock logos core land behind a new
ICoreRuntimeseam, with a JSON fixture, its own test suite and a standaloneshell-previewbinary that drives the shell’s QML from fixture data alone logos-basecamp#380 (mobile mock backend — Alex to expand) - The blockchain UI got its dashboard. New designs across the app: a section nav over dashboard, blocks, accounts and node views, stat tiles, node and validator status cards, block delegates and hash rows, plus chain-stats, Cryptarchia-info and status-config views and a new icon set logos-blockchain-ui#62
- Downloads have a progress bar, end to end — the downloader reports progress, the module carries it, PMUI and Basecamp draw it, and the design system’s progress-bar API was fixed to support them logos-package-downloader#36, logos-package-downloader-module#34, logos-package-manager-ui#77, logos-basecamp#382, logos-design-system#54
- The signer sheet says what the calldata does.
signer_uidecodes the transaction it is about to show — offline, from a static archive, with no client for any wallet, chain or RPC module, so what the human reads depends on that plugin alone. Only a call to an address the decoder can identify is labelled VERIFIED. Keystore account mutation became the custodian’s alone logos-evm-signer-ui#3, logos-evm-keystore-module#7 - A failed load is now reported as a failed load.
logos_core_load_moduleanswered 1 as soon as a child had been spawned — so a module whose plugin never loaded was listed byget_loaded_modulesand announced asloaded, while the real failure surfaced hops away. A four-repo chain closed it: the container gainedawaitLoadand a status line the host writes on stdout, the Qt host reports the loader’s own message (undefined symbol: logos_module_installrather than an exit code), and liblogos waits for that verdict, returning 0 and recordingerrorwhen it is bad logos-container#5, logos-container-subprocess#6, logos-module-loader-qt#12, logos-liblogos#200 - Two concurrent loads hung Linux CI for its full 6-hour timeout — and it was not a race in our code. Boost.Process’s POSIX launcher calls
notify_forkbetweenforkandexecve, walking every asio service and taking locks thatfork()inherited locked with no owner from threads that no longer exist. Spawning now goes throughposix_spawn, which runs no user code between fork and exec;vfork_launcherwas rejected as a trap (on macOSvforkhas been plainforksince 12.0, so a failedexecvewould be reported as SUCCESS). With spawning safe, loads stopped queueing behind each other: one globalloadMutex()had made every load wait on every other load whatever module it named, and four ordered locks replace it logos-container-subprocess#7, logos-liblogos#201 - A provider that restarts underneath you is now observable. A provider that unloaded and reloaded drove its replica out of
Validand back on the same node with the event helper still attached — the subscription survived, the stream resumed, and no subscriber could tell that hole from a module that had gone quiet. Protocol 0.9 adds a liveness watchdog, a generation counter and a status channel; it was then re-keyed by target module in place, because one handle per object name means all subscriptions to a module die and return together logos-protocol#77, #79, logos-cpp-sdk#153, #154, logos-qt-sdk#49, logos-rust-sdk#52
Initiatives
The load verdict — spawned, loaded and ready are three different facts
Built as a deliberate four-repo chain, each link inert until the one below it landed.
- The vocabulary:
LoadVerdict/LoadOutcomeandModuleContainer::awaitLoad(name, timeout), reported over the stdout the container already reads line by line — no fourth pipe, no new flag, and compatible in both directions. The default implementation returnsUnknown, which is the honest answer for a container that cannot observe its children logos-container#5, logos-container-subprocess#6 - The reporter: the host writes its status line before the event loop starts — the plugin loaded fact, not ready — on four failure paths, carrying the loader’s own message rather than an exit code logos-module-loader-qt#12
- The consumer: the load waits for that verdict and reports it. One defect, two platforms: eager binding (Linux) refuses at
dlopen, lazy binding (macOS) dies at the first call. It also closed an ordering race where a death arriving between launch andmarkLoadedwas erased by themarkLoadedbehind it logos-liblogos#200 - A crash stopped being describable by a stale teardown announcement:
markAllLoadedExitsExpected()marked every loaded module, the container then dropped the callback for a teardown it performed itself, and each unconsumed mark survived to describe that module’s next death as an orderly exit — a real crash reachingmodules_stateasstopping → unloadedinstead ofloaded → errorlogos-liblogos#199 - The snapshot stopped saying every module is untyped and unversioned:
typeandversionhad shipped hardcoded empty, two lines from the parsed metadata that already carried them;errordocumented as covering a failed load, not only a crash logos-liblogos#198, logos-modules-state-module#3 - Concurrency, once a load could block: per-module locking under a
fleet → module → configorder, core’sLogosAPIanchored to the owner thread rather than built lazily by whichever worker dialled first (measured 157 ms vs 156 ms — not assumed), and re-entrancy refused rather than allowed logos-liblogos#201 - fd hygiene came with the
posix_spawnmove: our six pipe ends markedCLOEXECat birth and everything above stderr closed, because enumeration cannot see a pipe another thread creates a moment later and marking cannot reach a descriptor we did not open logos-container-subprocess#7 - Relocked and bumped through: liblogos onto the fork-safe container and the relative-
--pathhost, then Basecamp, logosctl and the standalone app onto liblogos logos-liblogos#203, #204, logos-module-loader-qt#13, logos-basecamp#384, #385, logos-logoscore-cli#117, #118, logos-standalone-app#45, #46
Protocol 0.9 — subscription continuity, keyed by target module
- What 0.9 adds: a liveness watchdog polling each held handle at a 1 s cadence (on qt_remote that is exactly “the replica is still synced to its source”; on a transport with no notion of staleness it costs one virtual call and never fires), a per-subscription generation counter, and a status channel.
LP_SUB_LOSTfollowed byLP_SUB_ARMEDat a higher generation is the unrecoverable-gap marker logos-protocol#77 - The silent re-arm was kept, deliberately. Removing it is the obvious reading of “a re-established subscription is a new subscription” and is wrong: subscriptions made from
init()/onContextReady()depend on that deferred arm, so removing it would hand every consumer that has not adopted the callback silent event loss. It became observable instead logos-protocol#77 - Then re-keyed by target module, revising 0.9 in place rather than adding a 0.10:
m_handlesholds one handle per object name, so a per-subscription key gave a consumer N identical reports of one loss and admitted a state that cannot occur — two subscriptions to the same module diverging permanently. Four symbols were removed from a MINOR that is already on master;lp_subscribestayed verbatim throughout logos-protocol#79, #80, #78 - Carried into all three SDKs, each landing the per-subscription shape first and then following the re-key: the C++
subscribeExwas guarded on protocol ≥ 0.9 in the arithmetic-expanded form (the obviousMINOR >= 9spelling goes false at 1.0.0), so it merged as a no-op before the protocol side logos-cpp-sdk#153, #154, logos-qt-sdk#49, logos-rust-sdk#52 - The relock train: protocol 0.9 through plugin-qt, qt-sdk, module-builder and the modules that follow it; the consumer-admission bound raised 8 → 9 and then its note corrected — the bound stays at 9 logos-plugin-qt#31, #32, #33, logos-qt-sdk#47, logos-module-builder#222, #223, #224, #225, #227, logos-capability-module#29, logos-liblogos#197, logos-logoscore-py#23
- Two correctness fixes on the same surface: the 3-arg
informModuleTokenmarshalled to the owner thread, and nine qt-sdk tests that were failing on unmodified master because they still encoded the pre-0.8 double-write — the glue was right, the tests were stale logos-protocol#83, logos-qt-sdk#48
EVM — the approver becomes usable, and reads can be proved
- Tier D: account mutation (
create_mnemonic,import_*,new_account,export_keystore_json,delete_account) reachable only by a configured custodian. The gate runs before the arguments are parsed and every secret a refused call carried is zeroized; gatingdelete_accountclosed an unmetered, unattributable password oracle whose correct guess destroyed the account. An empty custodian admits nobody. The decision moved into a puregate.rs—glue.rsneeds a live runtime, so none of it had been unit-testable at all logos-evm-keystore-module#7 - Calldata decoded on the device that displays it: the interpretation is derived from
renderLinesthemselves, never fetched — so it cannot describe different bytes than the ones on screen — is additive rather than substitutive, and self-labelling: only a call to an address the decoder can identify says VERIFIED, because a selector proves nothing about a contract logos-evm-signer-ui#3 - The intent is a doorbell, not a courier.
evm.signing.approvecarries one keystore handle and nothing renderable, which is what keepsrenderLinesthe keystore’s own words and the requester attribution real. It could not be a courier anyway —approve()returns a count, and the signatures leave viafetch_resultto whoever holds the receipt, so the approver never receives what it authorised logos-evm-signer-ui#5, #4 - Optional light-client verified routing, per chain:
off(default) is today’s behaviour exactly andrequiredrefuses on failure — there is nopreferredmode, because quietly answering from an unverified source when the caller asked for verification is the failure the feature exists to prevent. Only proof-backed reads are classed verified; fee oracles, gas estimation and broadcast are forwarded on trust and say so logos-evm-eth-rpc-module#8 - A token list that works offline: 1709 tokens across 25 chains compiled in, with a
PROVENANCE.mdrecording URL, fetch date, version, byte count and sha256 so the blob is re-derivable rather than trusted — and mutators that had emitted nothing now diff a fingerprint of the served rows, so a list that stops resolving cannot empty its chains in silence logos-evm-token-list-module#5, logos-evm-fee-module#2
Download progress, end to end
- Progress reported by the downloader and carried through its module logos-package-downloader#36, logos-package-downloader-module#34, logos-package-downloader#35
- Drawn in PMUI (closing #57 and #61) and in Basecamp for both app and module downloads logos-package-manager-ui#77, logos-basecamp#382
- The design system’s progress-bar API fixed to support them, and a dot matrix added logos-design-system#54, #53
- PMUI flake hygiene alongside: why lgx is taken as headers, said correctly logos-package-manager-ui#76, #75
Basecamp — intents, install flow, and a testable app
- Intent routing completes the round trip: a caller that raises an intent is routed back once the provider has finished logos-basecamp#378
- The install/uninstall/upgrade ack handshake with PMUI is gone from the Basecamp side too, now that
modules_statereports lifecycle directly logos-basecamp#377 - A regression fixed: after a failed install attempt from PMUI, the package’s dependency stopped appearing in Basecamp’s confirmation popup logos-basecamp#381
- Mock backends and a mock logos core land, which is what makes the app drivable without a live stack logos-basecamp#380
- The MCP-driven UI test layer grew across workspace and sidebar behaviour, plus two App Manager fixes the tests needed — missing automation
objectNames, and an empty-search message when a non-empty query matches no apps logos-basecamp#344, #345, #346, #364, #365, #366, #367
watch() stopped hanging silently
logos.watch(backend.someSlot(...), …) unwrapped its argument unconditionally. When module() returned null the value is not a QRemoteObjectPendingCall, and .value<>() on a type mismatch default-constructs one whose isFinished() is never true — so neither callback ever fired, the watcher leaked, and nothing was logged. It presented as a blank panel, across all 82 call sites logos-view-module-runtime#29, #30
Modules, releases and the blockchain UIs
- Storage moved to 2.1.3 through the release set, its Nim core to 0.4.5, and the UI onto the new version logos-modules-release#53, logos-storage-module#84, logos-storage-ui#91
- The AMM modules added to the release set logos-modules-release#52
- New designs shipped in the blockchain UI; the execution zone’s
send_generic_public_transactioncorrected to a byte-string instruction IPC type logos-blockchain-ui#62, logos-execution-zone-module#49
Tooling, CI and diagnostics
- A Nim cdylib authoring path mirroring the Rust one, so a module can be authored with a Nim core behind its
.lidlcontract — verified end to end through.lgx, load and dispatch. Motivated by the Muster module’s published contract logos-module-builder#202 - Windows CI moved to the self-hosted pool.
build-windowskept losing its GitHub-hosted runner to capacity reclaim — a cancelled step, every later step skipped, and the job still reporting success. Onevars.WINDOWS_RUNNERexpression, with unsetting the variable as the rollback logos-basecamp#383, and the host-verdict doc-test now runs on Windows too logos-liblogos#202 - A CI failure that could not be read: a macOS-only test failure was #177 of 559, so its
--output-on-failureblock sat far above nix’s 25-line tail and was unreachable viagh run view --log-failed. Adding-Ltook the log from 1,066 lines to 26,369, after which the real bars could be set off what a loaded runner actually costs (median 156 ms vs 31–35 ms on an idle local box) logos-protocol#81, #82 - Doctest reports: content-addressed screenshot files landed and were then reverted — the split grew the branch and broke consumers — while Basecamp’s CI publishes the report directory and stops gh-pages growing forever logos-doctest#16, #17, logos-basecamp#379
- The mock transport re-reads
LOGOS_MOCK_FIXTUREinstead of latching the mode at first use logos-protocol#76
Appendix: all merged PRs, by repo
logos-basecamp, logos-blockchain-ui, logos-capability-module, logos-container, logos-container-subprocess, logos-cpp-sdk, logos-design-system, logos-doctest, logos-evm-eth-rpc-module, logos-evm-fee-module, logos-evm-keystore-module, logos-evm-signer-ui, logos-evm-token-list-module, logos-execution-zone-module, logos-liblogos, logos-logoscore-cli, logos-logoscore-py, logos-module-builder, logos-module-loader-qt, logos-modules-release, logos-modules-state-module, logos-package-downloader, logos-package-downloader-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-view-module-runtime
logos-basecamp
- chore(deps): bump logos-liblogos 634c958 -> a3bc9d9 (#385)
- chore(deps): bump logos-liblogos 73aaa92 -> 634c958 (#384)
- ci(windows): let build-windows target the self-hosted runners (#383)
- feat: show a progress bar during app and module downloads (#382)
- feat: resolve regression that after trying to install a package from … (#381)
- feat: mock backends and logos core for basecamp (#380)
- ci(doctests): publish the report directory, and stop gh-pages growin… (#379)
- feat: provide routing when an intent caller calls a request that rout… (#378)
- feat: remove the ack handling for pmui install/uninstall/upgrade and … (#377)
- Fix/MCP UI app manager empty search feedback (#367)
- Fix/MCP UI app manager automation object names (#366)
- Test/MCP UI sidebar active tile follows currentVisibleApp (#365)
- Test/MCP UI sidebar build label matches backend.isPortableBuild (#364)
- Test/MCP UI workspace re-clicking an open app does not create a second dock (#346)
- Test/MCP UI workspace closing the last dock brings the welcome page back (#345)
- Test/MCP UI workspace opening an app replaces the welcome page with a dock (#344)
logos-blockchain-ui
logos-capability-module
logos-container
logos-container-subprocess
- fix: spawn with posix_spawn, so the child cannot deadlock before exec (#7)
- feat: answer whether a launched module actually loaded (#6)
logos-cpp-sdk
- feat(lp): key subscription state by TARGET MODULE, not by subscription (#154)
- feat(lp): LpClient::subscribeEx — surface subscription transitions (#153)
logos-design-system
logos-doctest
- revert(report): inline screenshots again — the split grew the branch and broke consumers (#17)
- feat(report): write screenshots as content-addressed files, not inlined base64 (#16)
logos-evm-eth-rpc-module
logos-evm-fee-module
logos-evm-keystore-module
logos-evm-signer-ui
- feat: provide evm.signing.approve (#5)
- chore(deps): add a lockfile, and make both inputs follow our module-builder (#4)
- feat(signer_ui): decode the calldata it is about to show (#3)
logos-evm-token-list-module
logos-execution-zone-module
logos-liblogos
- chore(deps): take the host that resolves a relative —path (#204)
- chore(deps): take the fork-safe container, and say what spawnMutex really does (#203)
- ci: run the host-verdict doc-test on Windows too (#202)
- feat(core): load different modules concurrently, one at a time per module (#201)
- fix: report a failed load as a failed load (#200)
- fix: do not let an announced teardown describe the next crash (#199)
- fix: report each module’s real type and version in the modules_state snapshot (#198)
- chore: bump logos-protocol (#197)
logos-logoscore-cli
- chore(deps): bump logos-liblogos 634c958 -> a3bc9d9 (#118)
- chore(deps): bump logos-liblogos a2da65f -> 634c958 (#117)
logos-logoscore-py
logos-module-builder
- chore(deps): relock logos-rust-sdk — the per-target subscription surface (#227)
- chore(deps): relock onto the 0.9 revision — protocol and cpp-sdk together (#225)
- chore(deps): relock onto cpp-sdk 58c6573, which carries LpClient::subscribeEx (#224)
- chore(protocol): move to 0.9, and make logos-qt-sdk follow our logos-plugin-qt (#223)
- chore: bump logos protocol (#222)
- Add a Nim cdylib authoring path (codegen.nim) (#202)
logos-module-loader-qt
- fix(host): resolve a relative —path, and say which failure it is (#13)
- feat(host): report whether the plugin loaded, and why it did not (#12)
logos-modules-release
logos-modules-state-module
logos-package-downloader
logos-package-downloader-module
logos-package-manager-ui
- Feat/download progress (#77)
- docs(flake): say why lgx is taken as headers, correctly (#76)
- chore: bump the flake.lock (#75)
logos-plugin-qt
- chore(deps): relock logos-protocol onto the 0.9 revision (#33)
- chore(protocol): correct the 0.9 admission note; the bound stays at 9 (#32)
- chore(protocol): raise the consumer-admission bound 8 -> 9 (#31)
logos-protocol
- fix(client): marshal the 3-arg informModuleToken to the owner thread (#83)
- test(plain): set the promptness bar off the runner, not off an idle laptop (#82)
- test(plain): bars a loaded runner can meet, and a log that says when it cannot (#81)
- docs(protocol): say that BOTH 0.9 cuts report MINOR 9 (#80)
- feat(subscriptions): key continuity by TARGET MODULE, revising 0.9 in place (#79)
- fix(nix): the derivation version says 0.9.0, like the header it tracks (#78)
- feat(subscriptions): make a provider restart observable, at MINOR 0.9 (#77)
- fix(mock): re-read LOGOS_MOCK_FIXTURE instead of latching the mode (#76)
logos-qt-sdk
- feat(qt): key subscription state by TARGET MODULE, not by subscription (#49)
- fix(tests): assert token direction on the door protocol 0.8 actually uses (#48)
- chore(deps): protocol 0.9 and the raised consumer-admission bound (#47)
logos-rust-sdk
logos-standalone-app
- chore(deps): bump logos-liblogos 634c958 -> a3bc9d9 (#46)
- chore(deps): bump logos-liblogos e477bef -> 634c958 (#45)