* Identify requestModule caller from the RPC token, not fromModuleName. fromModuleName is leftover ABI: any loaded allowlisted name could be written there. Use logos::currentCaller() (host → core) for policy and the token push instead. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(deps): follow logos-cpp-sdk master for logos_caller.h module-builder's lock still had cpp-sdk 95d7b3, which does not ship the caller header requestModule now includes. Follow cpp-sdk master (937f17, #151). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(identity): fall back when mocks/old hosts omit the caller Direct impl tests and logoscore doctests do not always run under logos_module_set_call_caller. Use fromModuleName only in that case; a named currentCaller still wins, so spoofing the leftover ABI stays denied. Also follow qt-sdk, plugin-qt, protocol, and lidl master so qt-generator sees lidl/identity.hpp and the generated glue actually pushes the caller document. Co-authored-by: Cursor <cursoragent@cursor.com> * Revert fromModuleName fallback; simulate identity with CallCaller. requestModule identity is only logos::currentCaller(). Tests (and every other C++ module) open that stack with logos::CallCaller, the same push generated RPC glue performs. Pin cpp-sdk feat/call-caller-raii for that type until #152 lands. Co-authored-by: Cursor <cursoragent@cursor.com> * Take CallCaller from logos-test-framework, not cpp-sdk. The production SDK has no CallCaller; unit tests get the RAII stand-in via logos_test.h. Pin the test-framework branch until that lands on master. Co-authored-by: Cursor <cursoragent@cursor.com> * Track logos-test-framework master now that CallCaller landed (#7). Co-authored-by: Cursor <cursoragent@cursor.com> * Point the composition doctest at a host that actually injects the caller. logoscore-cli's lock still predates CallerScope and currentCallerJson, so requestModule saw Unknown and returned empty. Override protocol, plugin-qt, and the SDKs onto master for that build — the same dispatch path, not a fallback in capability. Co-authored-by: Cursor <cursoragent@cursor.com> * Rebuild logos_host via liblogos #186; root plugin-qt overrides never reached it. logoscore-cli does not follows plugin-qt into liblogos, so logos_host kept shipping without currentCallerJson. Pin liblogos to the protocol-0.8 branch and keep dumping the daemon log if this still fails. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
9.5 KiB
Logos Capability Module Specification
note: This document is a living document describing the current state of the capability module.
Table of Contents
1. Overview and Goals
The Capability Module is the broker that coordinates authentication tokens between Logos modules. When one module wants to call another, it requests a capability token instead of bypassing auth. The capability module issues a token, informs the target module about it, and returns the token to the requester so both sides share the same secret.
2. Architecture
2.1 Role in Logos
- Runs as a standard Logos plugin loaded by the core.
- Implemented as a
LogosModuleContextsubclass. The impl is Qt-free — zero Qt types in its own translation units; the contract is derived from the impl header and the Qt plugin glue is generated at build time. - Exposes a single RPC surface (
requestModule) so other modules or apps can obtain permission to call a target module. - Reaches the token store and the token-delivery path through
logos_host_services.h, the C++ veneer over the privileged C ABI (lp_token_keys,lp_inform_module_token_to). Those are gated by the host-services grant, which the host pushes into this module's image — the host decides who gets it, andhostServicesFor()namescapability_modulealone.
2.2 Tokens and Authentication
- Qt Remote Objects provides no built-in auth. The capability module issues per-pair tokens for inter-module calls.
- Tokens are stored in the shared
TokenManagerkeyed by module name. - When issuing a token, the capability module uses its own client to inform the target module of the new token so that the target's
ModuleProxycan validate subsequent calls.
3. API Description
3.1 Capability Module Interface
The impl class CapabilityModuleImpl derives LogosModuleContext. Its public methods are the
API — there is no dispatch marker; the generator derives the contract from the header:
| Method | Purpose |
|---|---|
requestModule(fromModuleName, moduleName) → std::string |
Generates a fresh token for the RPC caller (logos::currentCaller) to call moduleName, informs the target, and returns it. fromModuleName is leftover ABI and is not used for identity. Returns an empty string on any refusal — unnamed caller, unknown target, policy denial, or an unreachable target. |
registerRestriction(authToken, targetModule, allowedCallers) → bool |
Records an allowed-caller list for targetModule. Refused unless authToken is the trusted core/capability channel. |
Typed events would be declared under a logos_events: section. The module currently emits none.
4. Implementation
4.1 Module Structure
logos-capability-module/
├── src/
│ ├── capability_module_impl.{h,cpp} # CapabilityModuleImpl : LogosModuleContext — plain
│ │ # public methods; no Qt, no dispatch macros
│ └── capability_module.lidl # DEAD: the hand-committed contract from the
│ # `interface: "legacy"` era. Nothing reads it now —
│ # the contract in use is generated (step 1 below)
├── tests/ # Unit tests via logos-test-framework
│ ├── CMakeLists.txt
│ ├── main.cpp
│ └── test_capability_module.cpp
├── metadata.json # interface: universal + codegen{impl_class, impl_header}
├── flake.nix # mkLogosModule call
├── CMakeLists.txt # logos_module() call
└── docs/ # This document
Nothing under generated_code/ is checked in. As an interface: "universal" module the
builder runs three steps at build time:
| step | produces |
|---|---|
logos-cpp-generator --header-to-lidl |
capability_module.lidl — the contract, derived from the impl header |
logos-qt-host-generator --backend cdylib |
capability_module_cdylib_glue.{h,cpp} — the Qt plugin glue over the module-impl C ABI |
logos-cpp-generator --lidl --backend cdylib |
capability_module_module_impl.cpp — the Qt-free C-ABI export wrapper, plus capability_module_types.h |
There is no hand-written plugin loader and no logos_provider_dispatch.cpp. Both belonged to
the interface: "provider" path (logos-cpp-generator --provider-header, LOGOS_METHOD
dispatch, a LogosProviderBase subclass), which was removed — a module is a plain shared
library now, and making one a Qt plugin is a downstream hosting step.
4.2 Responsibilities
- Token issuance for inter-module calls: On
requestModule, mint a UUID token for the caller/target pair withboost::uuids::random_generator— deliberately the same CSPRNG-seeded generator the host uses, because the minted value is the auth token. - Inform targets of new tokens:
logos::host::informModuleTokenTo(), over anlp_clientcreated for the target, tells that module about the new token. (WasLogosAPIClient::informModuleToken_module—LogosAPIClientis a Qt type this Qt-free impl cannot use.) It authenticates withtokenFor(target)— the token this image holds under the target's name — not with anything belonging to the requester. - Central coordination: requests are not always granted. Identity is
logos::currentCaller()(the document the host pushed for this dispatch), notfromModuleName. Ungrantedtoken_registrystill refuses every request because the target lookup reads the registry. The target must be loaded, and a target with a registered restriction must list the token-bound caller. A target with no registered restriction is still unrestricted: that last gate is fail-OPEN by design during rollout (TODO(access-policy)in the impl), with deny-by-default as the end state.
4.3 Token Flow
- Caller invokes
requestModule(from, target).fromis leftover ABI. - The dispatch must carry a named caller (
logos::currentCaller: host →core, or a module name).targetmust be non-empty and hold a token, and the access policy must allow the token-bound caller. Any refusal returns an empty string and nothing is minted. - The auth token used for the push comes from
logos::host::tokenFor(target)— the token this image holds under the target's name. (Was a directTokenManagerlookup; the Qt-free impl goes through thelogos_host_services.hveneer instead. NotetokenForwrapslp_token_get, which is not gated — only enumeration vialp_token_keysis.) - It mints the UUID token, creates an
lp_clientfor the target, and callslogos::host::informModuleTokenTo(client, authToken = tokenFor(target), originModule = the target, moduleName = the REQUESTER, token = the new token, 3000 ms). The argument order is the trap: swapping the last two still compiles and still returns an ok-shaped status, while telling the wrong module about the wrong token. The 3 s timeout is deliberately shorter than the protocol default (20 s), so a module calling out from its own initializer fails fast instead of blowing downstream startup deadlines. - Returns the new token to the caller. Both sides now share the token for subsequent RPCs. If the push fails or times out, the caller gets an empty string instead.
5. Usage
5.1 Remote API Usage
Modules or apps call the capability module via Logos RPC (e.g., using generated wrappers or LogosAPIClient):
// Using generated wrappers
LogosModules logos(api); // api is a LogosAPI* for your module/app
QString token = logos.capability_module.requestModule("chat_ui", "waku_module");
The returned token must be used by the caller when invoking methods on the target module; SDK clients attach it automatically.
5.2 Metadata
metadata.json fields:
name:capability_moduleversion: semantic version stringdescription: describes token brokering/coordinationauthor: module author/maintainertype:coreinterface:universal— the header-first cdylib path. Withcodegen.impl_class/codegen.impl_headerit names the class the contract is derived from. Two earlier values:provideruntil22e54ff(aLogosProviderBase+LOGOS_METHODcodegen path that no longer exists), then the defaultlegacy— a handcraftedQ_OBJECT/Q_INVOKABLEQt plugin — untilfc39b1b.codegen:impl_class/impl_header— the class and header the contract is derived from (CapabilityModuleImpl,src/capability_module_impl.h)host_services:["token_registry", "token_delivery"]— the two privileges this module declares and the host grants, bound to its verified name. Ungranted, every gated call fails closed (see §2.1)capabilities: typically includesmodule_coordination,permission_managementdependencies: usually none (bundled with core)nix: build configuration consumed bylogos-module-builder(packages, external_libraries, cmake flags)