Files
Dario LipicarandClaude Opus 5 c670f7f2cf feat: capability_module as a universal module, over a host-granted trust root (#24)
* feat: capability_module as a universal module (C4)

Rewrites the trust root as a plain Qt-free C++ class over the host-services
veneer, replacing the hand-written Qt plugin that reached into TokenManager
directly. The RPC surface is preserved EXACTLY — lm reports
requestModule(QString,QString) and registerRestriction(QString,QString,
QStringList) identically before and after; only initLogos(LogosAPI*) is gone,
which is the legacy Qt init hook no universal module has.

  * getTokenKeys()      -> logos::host::tokenKeys()        [token_registry]
  * getToken(name)      -> logos::host::tokenFor(name)
  * informModuleToken_module -> logos::host::informModuleTokenTo [token_delivery]
  * QHash/QSet          -> std::map/std::set, mutex-guarded (the Qt original
                           was implicitly serialised by the event loop, which
                           is not a property to inherit silently)
  * constantTimeEquals  -> the std::string one in logos_host_services.h

Token minting uses boost::uuids::random_generator — deliberately the SAME
generator the host uses for each module's token (liblogos module_manager.cpp),
not a hand-rolled std::random_device formatter: boost seeds from the platform
CSPRNG, while std::random_device is permitted to be deterministic and
historically was on MinGW, which is a live target. This value IS the auth token.

The argument order of the delivery call is spelled out at the call site because
it is the trap: authenticate with the TARGET's token, origin_module is the
TARGET, module_name is the REQUESTER. Swapping the last two compiles and
returns an ok-shaped status while telling the wrong module about the wrong
token.

PROVEN AT RUNTIME: logos-test-modules ipc-tests FAIL -> PASS with this module in
place — a universal trust root minting tokens under a host-granted privilege.

Getting there needed a fix outside this repo. The grant was delivered to the
module's process and then dropped, because module-builder emitted the cdylib
glue with logos-qt-sdk's STALE copy of the generator instead of the maintained
one in logos-plugin-qt (both compile, so nothing failed). The explicit refusal
message this impl logs is what made that findable at all:

  [capability_module] REFUSING 'core_service': this module was not granted the
                      token_registry host service, so it cannot verify any caller

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(deps): rev-pin logos-module-builder at the builder this module needs

Second lock pass of the capability-module <-> module-builder <-> standalone-app
cycle. Pass 1 pushed the C4 universal rewrite against a builder that predates
it; this closes the loop.

  logos-module-builder  9d3b7cc -> c60d4a9  feat/sdk-codegen-b4-qt-host-repoint

The rev goes in the URL, not just the lock. c60d4a9 is a BRANCH tip, so the
plain `github:logos-co/logos-module-builder` url this repo had would let
`nix flake update` relock onto master (9ac3a15) and silently undo the bump.
Two commits that only exist past master are load-bearing here:

  85dfb34  parse and validate metadata.json#host_services (C2) — this module
           declares host_services [token_registry, token_delivery], and without
           the parser the veneer header is never wired in
  ed50731  emit cdylib glue with logos-qt-host-generator, not qt-sdk's stale
           copy — the out-of-repo fix this repo's own C4 commit names as its
           prerequisite

MEASURED, not assumed. Same tree (fc39b1b), old pin vs new pin:

  packages.default   9d3b7cc: EXIT=1  fatal error: 'logos_host_services.h'
                                      file not found (capability_module_impl.cpp:7)
                     c60d4a9: EXIT=0  /nix/store/fhshfnr5p5icb7bs3ybk0dlj9f7zqxdc-...

lib, generate, include, install and lgx are green at the new pin too (6/7).
The bump is a fast-forward: 9ac3a15 (master) is an ancestor of c60d4a9, and
9d3b7cc is an ancestor of 9ac3a15, so nothing on master is given up. Drop the
rev once the branch merges.

Every transitive rev this drags in was verified to be a live remote branch tip
before it was written here: logos-protocol c8bab12, logos-cpp-sdk a04b278,
logos-qt-sdk 8a06b87, logos-plugin-qt AND logos-plugin-core cc24fa1 (both, per
the type:core / ui split), logos-view-module-runtime 5510acd,
logos-standalone-app 39f4f2b, logos-test-framework c382ab1.

NOT fixed here, and pre-existing: checks.unit-tests fails at both pins with
"Cannot find source file: ../src/capability_module_plugin.cpp". fc39b1b deleted
the hand-written Qt plugin, and tests/ still compiles it and drives
initLogos()/TokenManager. That is a C4 follow-up, not a lock question — see the
level report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: describe the module this repo actually builds

docs.md still described the `interface: "provider"` shape this module was
migrated off. Every claim below was checked against the tree or the build
output, not against the commit history:

  * "Implemented as a LogosProviderBase subclass" — it derives
    LogosModuleContext, and the impl is Qt-FREE: zero Qt types in either of its
    translation units.
  * "exposes a single primary method via the LOGOS_METHOD marker" — there is no
    marker; the public methods ARE the API and the generator derives the
    contract from the header. There are also TWO of them: registerRestriction
    was missing from the table entirely.
  * `requestModule → QString` — it returns std::string.
  * "LogosAPI* is delivered via LogosProviderBase::onInit" — it reaches the
    token store and delivery path through logos_host_services.h, the veneer over
    lp_token_keys / lp_inform_module_token_to, gated by the host-services grant.
  * A `src/capability_module_loader.h` in the file tree — that file does not
    exist; the plugin entry point is generated.
  * "generated_code/logos_provider_dispatch.cpp … produced by
    logos-cpp-generator --provider-header" — neither the file nor the flag
    exists. Confirmed by building
    `.#logos-capability-module--capability_module-generate`: what lands is
    capability_module.lidl, capability_module_cdylib_glue.{h,cpp},
    capability_module_module_impl.cpp and capability_module_types.h.
  * `interface: provider` in the metadata section — it is `universal`.

The three-step build pipeline is now spelled out with what each step produces,
since "which file comes from where" was the thing the old text got wrong in
every particular.

The removed path is named as history rather than deleted outright: "there used
to be a LOGOS_METHOD dispatch" is the question a reader arrives with, and
leaving no trace of it invites the same confusion in reverse.

Found while sweeping for stale references to the deleted generator paths after
the same class of rot turned up in logos-cpp-sdk's docs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: correct the capability_module docs for the universal migration

docs.md and the README still described this module as a handcrafted Qt plugin
and, worse, claimed the request path "always grants requests". It does not:
requestModule gates on a known caller (logos::host::tokenKeys()), a known
target (tokenFor()), and the registerRestriction access policy, minting the
token only after those pass. The token-flow section had the ordering backwards
and routed the push through LogosAPIClient, a Qt type this now Qt-free impl
cannot use — it goes through logos::host::informModuleTokenTo() over an
lp_client.

src/capability_module.lidl is marked DEAD: the module publishes a derived
`lidl` output now, so headerContractLidl prefers that over the committed file,
and the hand-kept method list is the pre-migration surface (it still lists
initLogos, which universal modules do not have).

Also records, in the README, that `nix build .#unit-tests` does not configure:
fc39b1b deleted src/capability_module_plugin.{h,cpp} without migrating tests/,
which still compiles them and drives CapabilityModulePlugin. That is this
repo's ONLY check, so it needs porting before this branch merges.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test: port the unit tests to the universal shape, and fix the only check

fc39b1b moved this module to `interface: "universal"` and deleted
src/capability_module_plugin.{h,cpp}, but left tests/ compiling them. CMake
failed at generate, and since unit-tests is this repo's ONLY check, the whole
suite has been dark since.

Ported to drive CapabilityModuleImpl directly. No seam was added to the impl
and no production call site changed, which the "grant is per-image, not
per-instance" argument in logos_host_services.h asks for.

The push at the end of requestModule needs no fake: LogosMockSetup (which the
old suite already used) puts the process in LogosMode::Mock, where
MockTransportConnection::requestObject vends a MockLogosObject for any name and
its informModuleToken returns true. Everything else runs for real — the token
registry via lp_token_save, the token_registry gate via lp_token_keys,
lp_client_create, and logos::host::informModuleTokenTo. The only new
requirement is lp_grant_host_services, public C ABI.

Qt types are gone from the test's own surface (std::regex for the UUID shape,
std::set, std::string/std::vector); Qt survives only transitively through the
mock guard.

15 tests ported, 3 added — the grant now has fail-closed coverage the Qt shape
could not express (token_registry ungranted, token_delivery ungranted, an
unreachable target). 18 passed in 2ms.

Mutation-tested, 6 gates, one real build each. Four are load-bearing:
known-caller, known-target, access-policy, and registerRestriction's
trusted-token gate each fail the suite when disabled. Two are NOT, and are
labelled in-file as contract assertions rather than left to look like coverage:
the explicit ungranted() check and the empty-name gate are both shadowed by the
known-caller gate. The empty-TARGET half is shadowed more deeply still — it
survives with three gates removed, only because lp_client_create("") returns
nullptr, which nothing in this repo pins.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(deps): retire the logos-module-builder rev pin, track master

07dba1f pinned logos-module-builder at c60d4a9 (tip of
feat/sdk-codegen-b4-qt-host-repoint) because two things this module cannot
build without were only on that branch. Both are now on module-builder master
(8cd62c7), so the pin is dead weight and the plain url is correct again:

  * parsing and validating metadata.json#host_services — lib/parseMetadata.nix,
    whose trust-root allowlist names capability_module as the ONLY module
    permitted to ask for token_registry/token_delivery; covered by
    tests/test-parse-metadata.nix, which uses this module's exact declaration
  * emitting the cdylib glue with logos-plugin-qt's logos-qt-host-generator
    instead of logos-qt-sdk's stale copy — lib/modulePreConfigure.nix and
    lib/buildCppPlugin.nix; asserted by tests/test-module-pre-configure.nix

Checked as FILES on master, not by ancestry: the PRs behind them
(logos-module-builder#203, logos-plugin-qt#19, logos-cpp-sdk#138,
logos-qt-sdk#33, logos-protocol#59) were SQUASH-merged, so
`merge-base --is-ancestor c60d4a9 master` is correctly false and proves nothing.

Relocked with an explicit `nix flake lock --update-input logos-module-builder`
rather than a bare `nix flake lock`, which does not re-resolve an input that is
already locked even once its url stops carrying a rev. Verified the revs moved:

  logos-module-builder  c60d4a9 -> 8cd62c7  (master)
  logos-cpp-sdk         a04b278 -> 95d7b3a  (master, #138)
  logos-qt-sdk          8a06b87 -> 19c844f  (master, #33)
  logos-plugin-qt       cc24fa1 -> 9b2c64e  (master, #19)
  logos-plugin-core     cc24fa1 -> 9b2c64e  (same repo, the type:core path this
                                             module actually resolves through)
  logos-protocol        c8bab12 -> f4407ff  (master, #59)

flake.lock shrinks by ~82k lines as the duplicated closures collapse onto one
revision each. No rev-pinned github:logos-co input remains that this repo owns.

VERIFIED on the relocked tree, aarch64-darwin, real builds:

  nix build .#default                        EXIT=0
  nix build .#checks.<sys>.unit-tests        EXIT=0, 18 passed
  lib generate include lidl headers-qt headers-lp
  install install-portable lgx               all EXIT=0 with an out path

and the two properties the pin existed to guarantee, checked in the OUTPUT:

  * generated_code/capability_module_module_impl.cpp exports
    logos_module_grant_host_services() -> lp_grant_host_services(), and
    capability_module_cdylib_glue.cpp calls it from the host's `hostServices`
    property. Both sit behind `LOGOS_PROTOCOL_VERSION_MINOR >= 3`; the closure
    links logos-protocol 0.4.0, so the grant path is compiled IN, not out.
  * the shipped share/logos/capability_module.lidl is byte-identical to the
    DERIVED contract, not to the stale committed src/capability_module.lidl —
    confirming the derived contract wins, as docs/ claims.

README's file-tree line said flake.nix "rev-pins logos-module-builder"; updated.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore(deps): relock onto module-builder master after the C3 grant landed

logos-module-loader-qt#8 merged (acd07cf), so the host-services grant path this
module depends on is on master: module_initializer.cpp stamps the hostServices
property the generated glue reads, and qt_plugin_format_loader's hostServicesFor
supplies it for capability_module.

unit-tests: 18 passed against the relocked closure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 10:49:18 -03:00

42 lines
827 B
JSON

{
"name": "capability_module",
"display_name": "Capabilities",
"version": "1.0.0",
"description": "Coordinates permissions between modules",
"author": "Logos Core Team",
"type": "core",
"category": "security",
"main": "capability_module_plugin",
"dependencies": [],
"capabilities": [
"module_coordination",
"permission_management"
],
"nix": {
"packages": {
"build": [
"boost"
],
"runtime": [
"boost"
]
},
"external_libraries": [],
"cmake": {
"find_packages": [
"Boost"
],
"extra_sources": []
}
},
"interface": "universal",
"host_services": [
"token_registry",
"token_delivery"
],
"codegen": {
"impl_class": "CapabilityModuleImpl",
"impl_header": "src/capability_module_impl.h"
}
}