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_ui declares evm.signing.approve — so IntentBroker::activate loads 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 keeps renderLines the 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 ICoreRuntime seam, with a JSON fixture, its own test suite and a standalone shell-preview binary 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_ui decodes 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_module answered 1 as soon as a child had been spawned — so a module whose plugin never loaded was listed by get_loaded_modules and announced as loaded, while the real failure surfaced hops away. A four-repo chain closed it: the container gained awaitLoad and a status line the host writes on stdout, the Qt host reports the loader’s own message (undefined symbol: logos_module_install rather than an exit code), and liblogos waits for that verdict, returning 0 and recording error when 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_fork between fork and execve, walking every asio service and taking locks that fork() inherited locked with no owner from threads that no longer exist. Spawning now goes through posix_spawn, which runs no user code between fork and exec; vfork_launcher was rejected as a trap (on macOS vfork has been plain fork since 12.0, so a failed execve would be reported as SUCCESS). With spawning safe, loads stopped queueing behind each other: one global loadMutex() 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 Valid and 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 / LoadOutcome and ModuleContainer::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 returns Unknown, 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 and markLoaded was erased by the markLoaded behind 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 reaching modules_state as stopping → unloaded instead of loaded → error logos-liblogos#199
  • The snapshot stopped saying every module is untyped and unversioned: type and version had shipped hardcoded empty, two lines from the parsed metadata that already carried them; error documented 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 → config order, core’s LogosAPI anchored 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_spawn move: our six pipe ends marked CLOEXEC at 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---path host, 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_LOST followed by LP_SUB_ARMED at 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_handles holds 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_subscribe stayed 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++ subscribeEx was guarded on protocol ≥ 0.9 in the arithmetic-expanded form (the obvious MINOR >= 9 spelling 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 informModuleToken marshalled 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; gating delete_account closed an unmetered, unattributable password oracle whose correct guess destroyed the account. An empty custodian admits nobody. The decision moved into a pure gate.rsglue.rs needs 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 renderLines themselves, 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.approve carries one keystore handle and nothing renderable, which is what keeps renderLines the keystore’s own words and the requester attribution real. It could not be a courier anyway — approve() returns a count, and the signatures leave via fetch_result to 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 and required refuses on failure — there is no preferred mode, 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.md recording 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

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_state reports 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

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 .lidl contract — 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-windows kept losing its GitHub-hosted runner to capacity reclaim — a cancelled step, every later step skipped, and the job still reporting success. One vars.WINDOWS_RUNNER expression, 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-failure block sat far above nix’s 25-line tail and was unreachable via gh run view --log-failed. Adding -L took 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_FIXTURE instead 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

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