mirror of
https://github.com/logos-co/logos-capability-module.git
synced 2026-08-31 04:31:15 +00:00
* 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>
164 lines
9.5 KiB
Markdown
164 lines
9.5 KiB
Markdown
# 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](#1-overview-and-goals)
|
|
- [2. Architecture](#2-architecture)
|
|
- [2.1 Role in Logos](#21-role-in-logos)
|
|
- [2.2 Tokens and Authentication](#22-tokens-and-authentication)
|
|
- [3. API Description](#3-api-description)
|
|
- [3.1 Capability Module Interface](#31-capability-module-interface)
|
|
- [4. Implementation](#4-implementation)
|
|
- [4.1 Module Structure](#41-module-structure)
|
|
- [4.2 Responsibilities](#42-responsibilities)
|
|
- [4.3 Token Flow](#43-token-flow)
|
|
- [5. Usage](#5-usage)
|
|
- [5.1 Remote API Usage](#51-remote-api-usage)
|
|
- [5.2 Metadata](#52-metadata)
|
|
|
|
## 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 `LogosModuleContext` subclass. 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, and `hostServicesFor()` names `capability_module` alone.
|
|
|
|
### 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 `TokenManager` keyed 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 `ModuleProxy` can 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 with `boost::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 an `lp_client`
|
|
created for the target, tells that module about the new token. (Was
|
|
`LogosAPIClient::informModuleToken_module` — `LogosAPIClient` is a Qt type this Qt-free
|
|
impl cannot use.) It authenticates with `tokenFor(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), not
|
|
`fromModuleName`. Ungranted `token_registry` still 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
|
|
|
|
1. Caller invokes `requestModule(from, target)`. `from` is leftover ABI.
|
|
2. The dispatch must carry a named caller (`logos::currentCaller`: host → `core`,
|
|
or a module name). `target` must 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.
|
|
3. 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 direct `TokenManager` lookup; the Qt-free
|
|
impl goes through the `logos_host_services.h` veneer instead. Note `tokenFor` wraps
|
|
`lp_token_get`, which is **not** gated — only enumeration via `lp_token_keys` is.)
|
|
4. It mints the UUID token, creates an `lp_client` for the target, and calls
|
|
`logos::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.
|
|
5. 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`):
|
|
|
|
```cpp
|
|
// 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_module`
|
|
- `version`: semantic version string
|
|
- `description`: describes token brokering/coordination
|
|
- `author`: module author/maintainer
|
|
- `type`: `core`
|
|
- `interface`: `universal` — the header-first cdylib path. With `codegen.impl_class` /
|
|
`codegen.impl_header` it names the class the contract is derived from. Two earlier values:
|
|
`provider` until 22e54ff (a `LogosProviderBase` + `LOGOS_METHOD` codegen path that no longer
|
|
exists), then the default `legacy` — a handcrafted `Q_OBJECT` / `Q_INVOKABLE` Qt plugin —
|
|
until fc39b1b.
|
|
- `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 includes `module_coordination`, `permission_management`
|
|
- `dependencies`: usually none (bundled with core)
|
|
- `nix`: build configuration consumed by `logos-module-builder` (packages, external_libraries, cmake flags)
|