feat(sdk): make the by-name call path a supported API

The dynamic (by-name) invoke path already existed and was already ungated at
every layer — lp_client_create / lp_invoke in the C ABI, logos::LpClient above
it, and the Qt client above that. Nothing checked a host service; there was no
gate to open. What was missing was the ERGONOMICS, which is what turned a
supported capability into something callers reached around the umbrella to get.

Three additive pieces, no gate touched:

1. LogosModuleContext::moduleName() — the module's own registry name, i.e. the
   origin it authenticates as. The typed wrappers bake their origin in at
   codegen time; a by-name call has to state one, and a wrong origin
   authenticates as nobody and fails far from the call site. Set through a NEW
   `_logosCoreSetModuleName_`, deliberately not a fourth parameter on
   `_logosCoreSetContext_`: every generated provider calls that signature, so
   widening it would break each one until regenerated, for a value the
   generator knows statically. Set before the context, so moduleName() is live
   inside onContextReady().

2. LogosModules::dynamic(target) on the origin-bound umbrella — the untyped
   client, with the origin baked in exactly as the typed members' is, and
   cached per target because LpClient owns a connection. The typed members over
   metadata.json#dependencies stay the ordinary way to call another module;
   this is for the cases whose target is a runtime value (a proxy, a router).

3. LpClient::getMethods() over the already-exported lp_get_methods. Invoke
   without introspect is guessing — a caller that cannot ask what exists can
   only hardcode, and a wrong guess fails at runtime like a typo.

Verified: #default builds, and both checks pass (tests, generator-cli).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Dario Gabriel Lipicar
2026-08-18 16:51:48 -03:00
co-authored by Claude Opus 5
parent 1afcf46f4d
commit 1be71bbbeb
4 changed files with 74 additions and 0 deletions
+25
View File
@@ -1649,6 +1649,31 @@ QString makeUmbrellaHeaderFromDeps(const QJsonArray& deps, const QStringList& in
s << " std::map<std::string, std::unique_ptr<" << className << "::State>> m_"
<< ifaceName << "_bound;\n";
}
// Untyped, BY-NAME access to a module this umbrella does not wrap.
//
// The typed members above cover `metadata.json#dependencies`, which is
// the right default and stays the ordinary way to call another module.
// But the by-name path already exists at every layer beneath this one
// (lp_client_create / lp_invoke, logos::LpClient), so a consumer that
// genuinely needs it — a proxy, a router, anything whose target is a
// runtime value — has been reaching around the umbrella to get it.
// Exposing it here is what makes that a supported surface rather than
// an accident.
//
// The origin is baked in, exactly as the typed members' is: an origin
// is asserted, never borrowed, and a wrong one authenticates as nobody
// and fails far from the call. Clients are cached per target, mirroring
// the bind_ state map above, because LpClient owns a connection.
//
// Pair it with LpClient::getMethods() — invoke without introspect is
// guessing.
s << " logos::LpClient& dynamic(const std::string& target) {\n";
s << " auto& _c = m_dynamic[target];\n";
s << " if (!_c) _c = std::make_unique<logos::LpClient>(target, \"" << originName << "\");\n";
s << " return *_c;\n";
s << " }\n";
s << " std::map<std::string, std::unique_ptr<logos::LpClient>> m_dynamic;\n";
s << "};\n";
return content;
}