Logoscore Weekly Update — 2026-09-21

Highlights

  • The blockchain node’s dashboard was rebuilt around what an operator can act on. The status hero’s eleven states became six — replay folds into Bootstrapping, Stopped/Not connected into Not started — and progress now comes from the node’s pushed processedBlock stream rather than the status poll alone, so an arriving block vetoes the stale treatment and collapses the poll backoff instead of a busy-but-healthy node being reported as broken. Diagnosis ranks root cause over consequence, and names a chainsync protocol mismatch rather than blaming unreachable peers, which is the symptom it produces. Around the hero: peers, slot, height, LIB, tip and epoch as tiles that each carry an ⓘ explaining what the number means, plus chain ID, CPU, memory, disk, uptime, stake notes and the value a claimable voucher actually holds logos-blockchain-ui#65, #66, #68, #70, #71
  • A provider swapped faster than the one-second liveness poll was invisible, and its replacement’s events arrived on the old subscription under the old generation. Detection is event-driven now: every loss goes through setState(Suspect), which QtRO emits synchronously on every configured facade, so a handle that saw its source depart stops passing events even after QtRO re-validates the facade. Measured with logoscore reload-module over 20 swaps: 10/20 silent on macOS and 15/20 on Linux before, 0/20 after, with LOST reported ~50 ms after the old process’s last event. The same PR fixed a second bug its control test exposed — the generation counted subscribers, so five subscribe/cancel cycles on a healthy module took it from 1 to 6, which two consumers read as restarts logos-protocol#91
  • Mining and claiming moved into the UI, because mining is how a fresh node funds its own stake. PoW rewards are paid into the leader’s funding key, which is what PoS stakes from. A wizard screen configures the knobs and claim period and picks auto-claim targets from the accounts the config already tracks, with the leader funding key listed first and marked — it is the only choice that turns mined rewards into stake — and Confirm writes the whole section in one powConfigure, so a rejected target cannot discard the settings beside it. The Mining tab’s counts exist because auto-claim runs unattended and reports failures to the node log, which the app cannot read: a claimable count that climbs while nothing is ever claimed raises a warning on the card. Claims are counted from blocks, since the claim op is the only place a settled reward is visible — pow_claimable_rewards reports tickets waiting, not ones paid. Tickets-in-flight defaults to 2, not the node’s 4: each claim carries a proof and the batch must fit one Blend payload, which 4 overruns, so every claim is rejected and the tickets expire unclaimed logos-blockchain-ui#67
  • A module now ships its own contract and answers for it. lidl() -> tstr is reserved as a built-in alongside name() and version(), generated by every SDK, and the canonical document is installed into the package at assets/lidl/ — platform-independent root assets in the LGX format, extractable without unpacking a variant, which is what the bridge’s offline docs renderer and the Python code generator were waiting on. Two defects fell out of doing it properly: the Qt glue answered lidl("junk") with null and status ok while every other zero-parameter call was refused, and legacy -> void contracts are now normalised to an absent return clause rather than a sentinel type logos-lidl#12, #13, #14, logos-module-builder#247, logos-package#40, #42, logos-plugin-qt#42
  • Every amount in the app had been the node’s raw lepta count, because the node publishes no denomination and so nothing scaled it. Amounts now render in LOGOS (1 LOGOS = 10⁹ lepta, symbol LGO) with lepta kept as the wire format, so no hop loses digits. The formatting slices strings rather than going through Number(): a lepta figure runs past 2⁵³ — the faucet note is u64::MAX — and NoteSelector had been summing note values that way, reporting a silently wrong total. Nothing is rounded and trailing zeros are trimmed, so one lepta reads 0.000000001 LGO rather than a 0.00 that claims the balance is empty; only the locale’s own separator counts as the decimal point, since accepting . as well would make 1.5 mean 15 to a de user logos-blockchain-ui#69
  • A use-after-free dating to June was crashing the JSON-RPC bridge. requestObject()’s timeout branch deleted the facade it had just waited on — and a waitForSource timeout means precisely that the class definition never arrived, which is when the shared QConnectedReplicaImplementation still holds that facade as a raw pointer in m_parentsNeedingConnect; ~QRemoteObjectReplica is an empty body that never deregisters. The module’s next source publication walked that list into freed memory: SIGSEGV in QMetaObjectPrivate::connect. It needs a second live facade for the same name to be reachable at all, which is why it hit the bridge and not simpler consumers. The timed-out facade is parked and handed to the next waiter instead — parking alone would be unbounded — and the two crash cases are validated on the pre-fix tree: SIGSEGV 5/5 runs before, 602/602 green after logos-protocol#95
  • Accounts, Transfer and Channel Deposit became one Wallet section picked between by a left menu, with Rewards, Mining and Wallet gated on a running node so a dead node cannot be asked questions it has no answers to logos-blockchain-ui#72 — and the three controls that dashboard is built from landed in the design system: LogosStatCard, LogosStageLane and LogosInfoButton, plus a valueColor that tints a value without claiming a severity. severity did three things at once, so a value worth colouring but not worth flagging had to claim one and got an icon with nothing to say — for Info that icon is an ⓘ, indistinguishable from the info button beside it logos-design-system#56, #57, #58, #60, #61
  • A catalog can now draw packages from other catalogs. An includesUrl on the identity card points at a document listing the catalogs it draws from — whole catalog, named packages, or pinned versions — resolved at fetch time, so a composed catalog stays current without republishing and an included version keeps the url, rootHash and signature its origin published. Nothing is re-hosted. A drawn-in row is deliberately stamped with the configured repository’s url, because the listings group on that field and hide an empty section, so origin-stamping would render an aggregate catalog as nothing at all — which is why provenance travels separately and the Details panel is the surface that says Repository: My Distro (drawn from Team B) logos-modules-release-tool#8, logos-package-downloader#40, logos-package-manager-ui#83, logos-logoscore-cli#139
  • The plain TCP transport never reported a provider restart at all, and never recovered from one. PlainLogosObject inherited isValid() == true from a connection that was closed for good, so the watchdog had nothing to see, and nothing redialled because no production code calls reconnect(). It now reports loss through the same registry path as qt_remote and redials in the background on a bounded 5 s dial; the first connect adopts that same dial, so a peer that accepts TCP and never answers the handshake no longer hangs a consumer’s constructor for good; and a call made on the shared io thread starts the redial and fails at once rather than waiting out a redial that can only run on the thread it is blocking logos-protocol#92, #93, #94
  • A human was approving a fee no screen showed. The approval commitment covers a transaction’s fee fields and so does the signature, but render_lines never rendered them — a zero-tip transaction that sat unmined on mainnet for two days went through the signer looking like any other. Each leg now shows its max fee and priority fee in wei and gwei, Fee at most in the native coin, and a ** ZERO PRIORITY FEE — this may never be included ** flag. Underneath, a failed eth_feeHistory had been swallowed into an empty history, which reads as a chain with no base fee — so every tier came from eth_gasPrice with maxPriorityFeePerGas: 0, and tx_sender, which signs only type-2 transactions, turned that into a real zero-tip transaction logos-evm-keystore-module#21, logos-evm-fee-module#8
  • RLN stopped being something a client configures. A node’s RLN comes from the network preset its createNode config already carries, with rlnState and rlnStateChanged reporting Disabled/Initializing/Ready/Failed so a caller can read the same deployment back without keeping a second copy of the table. Every shipped preset has RLN off, which is what the networks page already documented, so no node’s behaviour changed — and the module is an optional dependency now, so a node on such a preset runs without the RLN stack installed at all logos-delivery-module#118, #126, logos-delivery-demo#32
  • One flake-input cycle was costing every bundler thousands of lock nodes. modules-state-module → module-builder → standalone-app → liblogos → modules-state-module cannot be expressed in a lock, so nix unrolls it and every consumer inherits the whole subtree once per path it reaches the module by. One follows = "" cut it: that module’s own lock 690 → 104, liblogos 880 → 294, Basecamp 3939 → 2767 and logosctl 16886 → 15714, with the bundled plugins byte-identical before and after logos-modules-state-module#5, logos-liblogos#221, logos-basecamp#425, logos-logoscore-cli#137, logos-standalone-app#57

Initiatives

Blockchain UI — a dashboard that explains itself

Eight PRs, from the status hero down to the unit every number is printed in.

  • The hero, and where progress comes from. Eleven states to six; losing the poll greys the last known state rather than replacing it, so a busy-but-healthy node is no longer reported as broken. processedBlock is subscribed rather than inferred — an arriving block vetoes the stale treatment and collapses the poll backoff — and get_time_info came off the status path so polling can run at 2 s catching up and 12 s online with no give-up-and-ask-the-user pause. The header took the Logos mark and “Blockchain Node”, At Headslot was dropped and Epoch added from current_epoch logos-blockchain-ui#65
  • Liveness stopped being one probe deep. A single missed probe had been declaring a busy node dead; three consecutive misses are now required, and any arriving block clears the count, since a module pushing blocks is alive whatever the transport says. A stop pressed from Running had also been sharing the fifteen-minute deadline meant for a stop queued behind a replay, leaving the button dead and silent throughout logos-blockchain-ui#67
  • Uptime, an asynchronous start, and a crash that is reported as one. The uptime clock is driven off the status reading and stopped by every path that leaves Running, so it cannot keep counting for a node that is gone; its tick tiers mirror the smallest unit the view renders. Start no longer blocks, and a module that dies during one is reported — a crashed node used to sit on “retrying in Ns” for good. Large figures shrink to fit rather than being abbreviated to K/M/B, which was hiding the digits an operator is checking logos-blockchain-ui#66
  • Chain identity, and the resources the node is actually using. get_chain_id is read once the node is Running and cleared on every transition out of it. CPU and memory come from the node process itself: the PID is resolved from modules_state’s ModuleRecord, and the first CPU sample of a PID is discarded rather than published as 0, because it is a delta against a previous sample that does not exist. Disk is a directory walk over the chain db, sampled at 20 s — it touches every file the db holds, and disk moves slowly enough that a 20-second figure is never misleading. A missing modules_state costs those two tiles and nothing else logos-blockchain-ui#71
  • Stake, shaped by the backend. Total, note count and addresses from wallet_get_leader_aged_notes. A transient failure keeps the last figure, because it is still true; going offline clears it, because leaving it up would assert a stake the node no longer has. An empty total means “not reported” and a reported 0 means nothing has aged — two different answers that had rendered identically logos-blockchain-ui#68
  • Every tile got an ⓘ that says what its number means — peers, slot, height, LIB, tip, epoch, Blend — with the claimable-vouchers card now showing the value a voucher holds rather than just a count, and the stage lane completing all of its stages logos-blockchain-ui#70, #71
  • LGO, everywhere, with lepta kept on the wire. Scaling text back to lepta lives in BlockchainBackend::leptaFromLgo, which owns the u64 bound and the error messages; QML only strips grouping and normalises the decimal point, and the field validator refuses the other separator so the ambiguous form cannot be typed logos-blockchain-ui#69
  • One Wallet section — Accounts, Transfer and Channel Deposit behind a left menu, sharing one SectionNav with the rest of the app; Rewards, Mining and Wallet cannot be opened without a running node, which takes all three wallet panels with them logos-blockchain-ui#72
  • The design-system side. LogosStatCard, LogosStageLane and LogosInfoButton ship with storybook pages and unit tests, plus a compact size and Danger variant on LogosButton and a borderGlyph token; then the card gained states, an externally-filled info dialog, valueColor, and two caption fixes logos-design-system#56, #57, #58, #60, #61

Canonical LIDL — the contract travels with the module

  • The identity method. lidl() -> tstr is reserved and injected alongside name() and version(), exposed through the C API and the canonical serializer, with canonical output stability tested rather than assumed logos-lidl#12
  • Generated by every SDK, as a built-in that needs no trait code on the Rust side, with goldens and provider fixtures regenerated logos-cpp-sdk#162, logos-rust-sdk#61, logos-plugin-qt#38, logos-qt-sdk#56
  • Bundled into the package. Contracts are normalised before code generation, so #lidl, the generated lidl() and the package asset are one document; a core module installs its own plus those of its dependencies, interface_dependencies and optional_dependencies, UI plugins get dependency contracts only, identical copies are deduplicated and same-name conflicts refused. End to end, assets/lidl/minimal.lidl is byte-identical to the minimal module’s #lidl output logos-module-builder#247, nix-bundle-lgx#15, nix-bundle-logos-module-install#8, logos-tutorial#91
  • Platform-independent root assets in the LGX format, preserved across a merge, with paths and conflicts validated — and extractable on their own, since a program that only wants a contract had to unpack a whole variant, binaries included, to get one. Two are waiting on it: the bridge’s offline docs renderer and the Python code generator logos-package#40, #42
  • lidl("junk") answered null with status ok. The Qt glue answers the generator-owned lidl() itself, before logos_module_dispatch runs, and its own argument check returned an empty QVariant — while name("junk"), version("junk") and every contract method with the wrong arity were refused. The providers were never the problem: calling dispatch directly on each plugin already answered invalid_args. Conformance cells landed green on the relock logos-plugin-qt#42, logos-module-builder#252, logos-test-modules#64, #63
  • No-return methods are now structural — an absent return clause, serialized as method notify(), with historical -> void accepted only to migrate it away, so nil stays available for its CDDL null meaning instead of doubling as an absence marker. Carried through all four generators and the builder’s legacy contracts logos-lidl#13, logos-cpp-sdk#163, logos-rust-sdk#62, logos-plugin-qt#40, logos-qt-sdk#57, logos-module-builder#249
  • A lidl command-line tooljson, check, fmt, with typed exit codes — so scripts and other languages reach the canonical parser, identity pass, validator and serializer without linking the C ABI; the bridge’s Python SDK uses it and its CI checks that readers handle what writers produce. Measured: lidl fmt returns 134 of the 138 .lidl contracts in the local store byte-for-byte, and the other four differ only in their legacy -> void lines logos-lidl#14
  • Multi-dispatch became a bounded pool. One QThread per call was exhausting process descriptors under a request burst (QThreadPipe: Unable to create pipe: Too many open files, on macOS); a reusable QThreadPool with an optional max_workers in metadata replaces it, CPU-sized when omitted, with explicitly capped workers pre-created before networking load can exhaust the pipes logos-plugin-qt#39, logos-module-builder#248, logos-tutorial#92

The protocol — restarts you can see, and a replica that must not be freed

  • Why polling could not see a fast swap, and an event can. QtRO re-attaches the same replica facade to the replacement source, so by the next poll the handle is valid again and its event helper is already receiving the new process. Every loss, though, goes through setState(Suspect), which is emitted synchronously on every configured facade — so a bound handle stops passing user events at the loss and cannot be revived under the old generation whatever order the event loop runs things in logos-protocol#91
  • The generation now counts establishments, not subscribers. The header tells lp_subscribe callers to detect gaps by watching it, and two consumers act on it today, so five subscribe/cancel cycles on a healthy module reading as five restarts was a live defect, not cosmetics logos-protocol#91
  • Plain transport, three PRs deep. Detection (a failed plain connection never reopens, so isValid() can report it and the existing 1 s poll suffices), a bounded background redial, the first connect bound by that same dial with a 5.5 s cap because a busy io thread cannot fire its own deadline, and finally the io-thread case: a call from an event handler that waits on a redial is waiting on work that can only run on the thread it is blocking logos-protocol#92, #93, #94
  • Parked, not deleted. The timed-out facade is parented to m_pendingAcquires, which both the destructor and reconnect() destroy before the node, so it can never outlive the implementation pointing at it; takeParked() hands it to the next waiter, bounding the count by concurrent waiters rather than by retries. Uninitialized is the test, because for a dynamic replica that state means exactly “the implementation has no metaobject yet”, which is exactly when it holds the facade raw logos-protocol#95
  • Two propagation waves, seven steps each, because a module plugin carries its own static copy of the transport: cpp-sdk and plugin-qt, then qt-sdk and view-module-runtime, then module-loader-qt and test-framework, then liblogos, the standalone app, module-builder — the step that puts the fix into every module built with it — and finally Basecamp and logosctl. Neither carried a MINOR bump, so plugin-qt’s consumer-admission bound stayed put logos-cpp-sdk#164, #165, logos-plugin-qt#44, #45, logos-qt-sdk#58, #59, logos-view-module-runtime#34, #35, logos-module-loader-qt#17, #18, logos-test-framework#10, #11, logos-liblogos#216, #218, logos-standalone-app#53, #54, logos-module-builder#255, #256, logos-basecamp#422, #424, logos-logoscore-cli#135, #136, logos-test-modules#65
  • And a second liblogos relock for the modules it bundles, since capability_module and modules_state link the transport statically from whichever builder their repos pin — measured in the shipped liblogos-bin: the dylib exported takeParked, neither bundled plugin did logos-capability-module#31, logos-modules-state-module#4, logos-liblogos#220, logos-standalone-app#55

Catalogs that draw from other catalogs

  • The format half. includesUrl is reached from the identity card, never from index.json — the index is regenerated wholesale on every publish and on a 6-hourly cron, so a hand-authored entry there is destroyed on the next rebuild. The card points rather than carries, the same split indexUrl already makes, and both hand-edited files got a validator logos-modules-release-tool#8
  • The client half. Resolution at fetch time, folded into the including repository’s listing, with ranges going through the existing semverRangeMatches so the dialect cannot drift from the resolver’s. Provenance rides in new origin* fields on the entry and on every version entry, because after a merge one package’s versions can come from several catalogs logos-package-downloader#40, logos-package-downloader-module#39
  • Rendered in both frontends, and in the CLI. The Details panel follows the version picker, so it cannot keep naming whoever published versions[0] while showing another catalog’s version and hash beside it, and falls back to the configured repository when the origin is absent or empty. catalog ls prints the declared includesUrl with the resolved catalogs nested under it — including one that resolved to nothing, since a catalog whose includes document is unreachable must not read as one that never meant to draw from anybody logos-package-manager-ui#83, logos-logoscore-cli#139, #140, logos-basecamp#428
  • Two PMUI fixes rode along. A locally-installed package reported Dependencies: None however many its manifest declared — the synthetic “Local” row hardcoded the field, and for a package outside every catalog that row is the only surface there is; and the version cell now tooltips what it elides logos-package-manager-ui#82, #80

EVM — fees on the screen that signs them, and a router for any pair

  • A bundle priced as the chain will find it. estimate_bundle prices calls leaving in order from one account: an ERC-20 approve in an earlier call becomes an eth_estimateGas state override on that token’s allowance slot for every later one, so a swap behind its approval gets a real estimate instead of execution reverted: STF. The slot is found rather than assumed — one eth_call of allowance(owner, spender) carries 64 sentinels across the Solidity and Vyper layouts, and only the slot the token actually read is overridden (USDC: slot 10). Tiers moved to p10/p30/p60, because the top of a block’s reward distribution is MEV — p90 measured at ~1 gwei against a 0.07 gwei base fee logos-evm-fee-module#6
  • A fee read that fails is an error, never a zero tip — and a fee field the caller sets alone is honoured rather than dropped without a word, which is what the wallet’s Advanced section produces when a user fills in only the priority fee logos-evm-fee-module#8, #9
  • A complete quoter and encoder for any pair. V2 direct and via WETH, V3 direct on every fee tier and via WETH on every tier pair, all in one Multicall3 batch; a one-thousandth probe beside every route stands in for the marginal price, so the winner’s shortfall against it is priceImpactBps; with an owner the same batch reads balance and per-router allowance, so the reply says whether the swap is affordable and whether an approval must go first. V3 is encoded for SwapRouter02 and wrapped in its multicall with the deadline — the legacy router has none, Sepolia has no legacy router at all, and Base’s seeded router already was SwapRouter02 but was being encoded for the legacy one logos-evm-uniswap-module#10
  • Refusals relayed whole. A closed verified-proxy gate came back as its error text wrapped in a new object, losing code and verifiedProxy — the verdict and the action to offer — so a caller could not tell “the proxy is not running” from any other failure logos-evm-uniswap-module#12
  • An EIP-712 typed-data leg, carrying the standard’s own JSON and computing the signing hash itself rather than trusting one from the requester, rendering domain, primary type and every message field with nesting indented under it. A document is refused when its message carries a field the declared type lacks — it would be shown but signed by nobody — when a declared field is missing, or when it contains a control character logos-evm-keystore-module#15
  • A displaced approval stays approvable. The Signer shows one at a time, and acknowledging a newer one demoted the record on screen back to Offered with its offered_at kept — so the abandoned-offer sweep expired it 60 s later, although a human had opened it. A record is claimed once acknowledged, and the sweep now collects only offers nobody came for logos-evm-keystore-module#19, #20
  • Reads answered while a token list downloads. refresh_now downloaded each list URL in turn on the module’s only call thread, so the wallet backend lost every token read for the duration and could prepare no send, native ones included. Declared concurrency: multi with the store behind an RwLock held only to read the plan and apply the result: measured against a 15 s list, list_offered went 14.2 s → 0.2 s, while the refresh itself still takes as long as its slowest list rather than their sum logos-evm-token-list-module#11, #8
  • A reusable chain registry, each default chain offered once per device, eth_feeHistory’s block count kept hex on the verified leg, and account wallet provenance exposed as one ungated read so the wallet backend composer stops recreating that join across module calls logos-evm-eth-rpc-module#14, #16, #17, logos-evm-keystore-module#16
  • The stack versioned together — all 18 packages at 0.1.0 with every in-stack dependency at ~0.1.0, merged in three waves because the loader refuses an out-of-range dependency and the doctests build each dependency from its repo’s main. The specs now name today’s callers too, each one read from that module’s glue.rs and metadata.json on its default branch rather than from other docs logos-evm-keystore-module#17, logos-evm-eth-rpc-module#15, logos-evm-token-list-module#9, logos-evm-fee-module#7, logos-evm-uniswap-module#11, logos-evm-signer-ui#17, logos-evm-eth-rpc-module#18, logos-evm-keystore-module#22, logos-evm-uniswap-module#13, logos-evm-signer-ui#18, logos-evm-net-proxy#4
  • Hygiene around it: the signer took logos-tx-decoder’s swap reading, and the modules caught their locks up to each other’s mains; the keystore’s two table tests stopped writing into the shared $TMPDIR, where they left encrypted vaults behind on a persistent runner and two concurrent runs collided outright; and the token-list README stopped telling callers to gate init_defaults on config_status, which only ever added a hop — it answers applied: false once a list is configured, and it checks inside the same &mut self call that writes logos-evm-signer-ui#16, #19, #20, logos-evm-fee-module#10, logos-evm-uniswap-module#14, logos-evm-keystore-module#18, logos-evm-token-list-module#10

Delivery and RLN — the preset decides

  • configureRln is not a module method any more. A preset → RLN settings table lives in the module, LOGOS_DELIVERY_RLN_PRESETS names a JSON file merged over it for a deployment that is not one of the shipped presets, and createNode installs the library’s RLN plugin synchronously before bringing the backend up on its own thread — because the library reads the plugin at node creation, so a later call would silently do nothing logos-delivery-module#118, #120
  • Optional, and therefore absent. liblogos_rln_module moved to optional_dependencies — a re-land, since the first attempt was reverted when module-builder 0.2.5 could not parse the key and 0.3.0 can. A node whose preset has RLN off, which is every shipped preset, now runs without the RLN stack installed logos-delivery-module#126, #122
  • A message now says where it came from. source (live/history) joins messageReceived, because store catch-up is on by default and a restart replays what the node missed while it was down, which consumers had no way to tell from live traffic; and messageQueued surfaces a send held back because the epoch’s rate-limit budget is spent — not rejected, so the usual terminal events still follow logos-delivery-module#115, #121, logos-delivery-demo#31
  • The demo shows RLN state rather than collecting it — a badge beside the connection badge, showing Disabled rather than hiding itself, since that is what every shipped preset reports; the RLN panel reads its (registry, identifier) scope back from rlnState once bring-up reaches Ready logos-delivery-demo#32
  • librln is copied in prebuilt, so nix scanned nothing. The libiconv it names by absolute store path is zerokit’s dependency, not the module’s, and a tar is opaque to reference scanning — so the path was registered as a reference of nothing and the plugin failed to dlopen wherever it was absent. It only ever arrived as a side effect of compiling the module; a warm cache is what exposed this class, and a cold one hid it logos-delivery-module#74, #112, #124, #119
  • basecamp:// deep links. A URL scheme registered per platform, a single-instance guard, an inbox that holds a link arriving before the shell is ready, and a coordinator that turns it into the same intent an in-app request raises — 41 files, including the macOS Info.plist and the .desktop entry logos-basecamp#392
  • “It would be installed from raw.githubusercontent.com” is the same string for every package published through GitHub, so it named nobody while reading like an answer. The publishing repository is shown instead — the name every other view spells it by, selectable, with a copy button and a page that opens — composed from the downloader’s sourceOwner/sourceRepo, and used on the install-confirmation dependency rows too, where two forks of one catalog had rendered identically logos-basecamp#418
  • Three more things the consent flow got wrong, all in the same PR: a chooser titled “Choose an app” over a list of one, where the broker deliberately sends a lone provider to be confirmed; approving by clicking a list row while refusing was a button — the safe action prominent, the intended one an affordance that does not look like one; and, with one provider installed, catalog packages that could also service the intent simply not shown, on the only screen where what a package provides is visible at all logos-basecamp#418
  • Told what to type. A second instance now prints the --new-instance invocation for the same directory rather than only refusing, and the A1–A17 UI suite dropped its dead branches and retry loops that could only pass instantly or spin logos-basecamp#417, #415, #419

Build, locks and process lifetime

  • A module host died with the thread that loaded it. logos_host arms PR_SET_PDEATHSIG(SIGKILL), and Linux ties that signal to the thread that spawned the child, not the process — while liblogos calls load() on whichever thread called logos_core_load_module, and its header describes off-thread loads as safe. Every POSIX child is now spawned from one dedicated process-lifetime thread. It was visible all along: once the thread-safety suite loaded real modules, every passing Linux run logged ~220 Module process crashed lines and nothing asserted on them logos-container-subprocess#9, logos-liblogos#215, logos-test-modules#62, logos-module-loader-qt#16
  • A .lgx is a gzipped tar, so nix recorded no references for it — and #install is built from that archive, so it never saw the plugin’s store paths either. On a binary-cache hit the plugin’s libraries were simply missing. The derivation takes its payload’s runtime closure now, checked by an install-closure that dlopens the installed plugin in a sandbox holding only that closure nix-bundle-lgx#7, logos-module-builder#254, nix-bundle-logos-module-install#9
  • External libraries, three ways they were wrong. Staged copies kept the store’s read-only modes, so fixCmakeFiles could not rewrite a library shipping a CMake package; test builds did not resolve bare flake inputs or copy nested headers the way the module build does; and the test rpath was joined with : — one CMake list entry, invisible on Linux where ELF joins with : anyway, and a dyld abort on macOS where each entry becomes its own LC_RPATH. Plus a way for an entry to say where an input publishes a given system’s build, since zerokit ships its MinGW build under packages.x86_64-linux and we will not own every library a module wraps logos-plugin-qt#41, logos-module-builder#250, #251, #253, logos-test-framework#9, #8
  • Checks that nothing built, and runners that swallowed failures. Seven plugin-qt checks had no CI step, so a guard now fails the job naming any check no step builds — assigning the eval result to a variable first, because for c in $(nix eval …) turns a failed eval into a loop that runs zero times and passes. nix run .#tests exited 0 when a test binary failed, in libp2p as in storage logos-plugin-qt#43, logos-libp2p-module#110
  • Windows. WinSCard.dll — the PC/SC smart-card API, shipped in System32 since XP, reached through keycard support in wallet_ffi — joined the system-DLL list that the payload gate checks, which is what let lez_core’s Windows package build; storage-module gained an x86_64-windows target simply by taking its systems from the builder rather than a hard-coded four-element literal; and storage-ui dropped an unused krb5 that is an evaluation-time hard failure for a mingw host nix-bundle-lgx#16, logos-storage-module#79, logos-storage-ui#86
  • Storage config migration moved into the module, since the storage module is becoming a dependency of both logosctl and Basecamp and the migration path has to be one path; storage-ui dropped its own copy and calls refreshConfig, and its plugin entry point is now generated rather than hand-written, the shape chat-ui already uses logos-storage-module#86, #82, #89, logos-storage-ui#92, #88
  • Releases and the rest: the module catalog takes its runners and the Logos cache from repo settings and merged its base repo in as a real merge so later syncs are a plain git merge; lgx sign --key accepts a path, since CI secret stores materialise keys under arbitrary names; dialWithAddrs reaches forceDial and spends one timeout on connect plus dial; getModuleStats carries the pid that a per-module stat must be joined by; and the chat module publishes its API reference to GitHub Pages, with fork PRs building their own commit in the doc-tests logos-modules-release#61, #62, #60, #63, logos-package#41, logos-libp2p-module#109, logos-liblogos#217, logos-chat-module#68, #71, #72, logos-chat-ui#61, logos-logoscore-py#24, logos-execution-zone-module#57

Appendix: all merged PRs, by repo

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-evm-eth-rpc-module, logos-evm-fee-module, logos-evm-keystore-module, logos-evm-net-proxy, logos-evm-signer-ui, logos-evm-token-list-module, logos-evm-uniswap-module, logos-execution-zone-module, logos-liblogos, logos-libp2p-module, logos-lidl, logos-logoscore-cli, logos-logoscore-py, logos-module-builder, logos-module-loader-qt, logos-modules-release, logos-modules-release-tool, logos-modules-state-module, logos-package, 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-test-framework, logos-test-modules, logos-tutorial, logos-view-module-runtime, nix-bundle-lgx, nix-bundle-logos-module-install

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-evm-eth-rpc-module

logos-evm-fee-module

logos-evm-keystore-module

logos-evm-net-proxy

logos-evm-signer-ui

logos-evm-token-list-module

logos-evm-uniswap-module

logos-execution-zone-module

logos-liblogos

logos-libp2p-module

logos-lidl

logos-logoscore-cli

logos-logoscore-py

logos-module-builder

logos-module-loader-qt

logos-modules-release

logos-modules-release-tool

logos-modules-state-module

logos-package

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-test-framework

logos-test-modules

logos-tutorial

logos-view-module-runtime

nix-bundle-lgx

nix-bundle-logos-module-install