mirror of
https://github.com/logos-co/logos-protocol.git
synced 2026-08-30 21:41:10 +00:00
feat(proxy): answer name()/version() for a provider that does not (#61)
Module identity should be total: every module answers name() and version(),
whatever built it. A module generated through the LIDL frontend now has both in
its own dispatch, but that leaves the rest -- already-built .lgx packages, ui /
ui_qml plugins, any provider whose dispatch does not answer -- reporting
nothing.
Every provider already knows both, through the providerName() /
providerVersion() vtable slots LogosProviderObject has always had. ModuleProxy
answers from those, so those modules gain identity with no edit to any of them.
Two placement decisions do the work:
* the dispatch fallback runs AFTER m_provider->callMethod. An invalid
QVariant is that slot's "unknown method" answer, so a provider that DOES
implement name() keeps its own result -- nothing existing changes
behaviour. It is also gated on an empty argument list, so a module with its
own name(which) reaches its dispatch exactly as before.
* getPluginInterface() advertises the same two methods when the provider does
not list them. Without this a module would ANSWER a method it claimed not
to have: present to whoever already knew to ask, invisible to `lm` and to
every untyped caller. Additive only -- an entry the provider already lists
wins, keeping its description and parameters.
Identity is a method, not introspection, so it stays behind the auth gate. The
three getPlugin* calls are ungated on purpose (they precede the token
exchange); these are not.
This is the one place both transports converge -- the plain transport publishes
a ModuleProxy and reaches it through QMetaObject::invokeMethod -- so one change
covers qt_remote and plain alike.
475/475 tests pass, 6 new.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f4407ff485
commit
43cd059608
+60
-2
@@ -121,7 +121,28 @@ QVariant ModuleProxy::callRemoteMethod(const QString& authToken, const QString&
|
||||
// (mnemonics, passwords, tokens, key material). Log only the method name and
|
||||
// the argument count, matching the other transport call sites.
|
||||
qDebug() << "ModuleProxy: callRemoteMethod" << methodName << "args:" << args.size();
|
||||
return m_provider->callMethod(methodName, args);
|
||||
const QVariant result = m_provider->callMethod(methodName, args);
|
||||
|
||||
// Module identity, for a provider whose own dispatch does not answer it.
|
||||
//
|
||||
// A module built through the LIDL frontend has name()/version() generated
|
||||
// into its dispatch, so it never reaches here. A legacy module derives no
|
||||
// contract and has neither — yet every provider already knows both, via the
|
||||
// providerName()/providerVersion() vtable slots the interface has always
|
||||
// had. Answering from those makes identity uniform across every module in
|
||||
// the fleet without touching a single one of them.
|
||||
//
|
||||
// Placed AFTER dispatch, deliberately: an invalid QVariant is this slot's
|
||||
// "unknown method" answer, so a provider that DOES implement name() keeps
|
||||
// its own result and nothing existing changes behaviour. Gated on an empty
|
||||
// argument list so a same-named method taking arguments is untouched.
|
||||
if (!result.isValid() && args.isEmpty()) {
|
||||
if (methodName == QLatin1String("name"))
|
||||
return QVariant(m_provider->providerName());
|
||||
if (methodName == QLatin1String("version"))
|
||||
return QVariant(m_provider->providerVersion());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -237,7 +258,44 @@ QJsonArray ModuleProxy::getPluginInterface()
|
||||
if (!m_provider) return QJsonArray();
|
||||
|
||||
qDebug() << "[LogosProviderObject] ModuleProxy: calling LogosProviderObject::getMethods()";
|
||||
return m_provider->getMethods();
|
||||
QJsonArray iface = m_provider->getMethods();
|
||||
|
||||
// Advertise module identity for a provider that does not list it itself.
|
||||
//
|
||||
// The dispatch fallback in callRemoteMethod answers name()/version() for
|
||||
// every module; without this, a legacy module would ANSWER them while `lm`
|
||||
// and every untyped caller reported it had no such method — present to
|
||||
// whoever already knew to ask, invisible to everyone else. The two have to
|
||||
// agree, so they are derived from the same providerName()/providerVersion().
|
||||
//
|
||||
// Additive only: an entry the provider already lists wins, so a module with
|
||||
// a generated (or hand-written) name() keeps its own description, signature
|
||||
// and parameters.
|
||||
auto lists = [&iface](QLatin1String name) {
|
||||
for (const QJsonValue& v : iface)
|
||||
if (v.isObject() && v.toObject().value("name").toString() == name)
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
// Signatures only -- this listing describes the interface, it does not
|
||||
// carry values. The VALUES come from the same two provider accessors in
|
||||
// callRemoteMethod, which is what keeps the listing and the answer in step.
|
||||
const struct { QLatin1String name; const char* desc; } identity[] = {
|
||||
{ QLatin1String("name"), "The module's name, as declared in its metadata." },
|
||||
{ QLatin1String("version"), "The module's version, as declared in its metadata." },
|
||||
};
|
||||
for (const auto& id : identity) {
|
||||
if (lists(id.name)) continue;
|
||||
QJsonObject entry;
|
||||
entry["name"] = QString(id.name);
|
||||
entry["type"] = QStringLiteral("method");
|
||||
entry["signature"] = QString(id.name) + QStringLiteral("()");
|
||||
entry["returnType"] = QStringLiteral("QString");
|
||||
entry["isInvokable"] = true;
|
||||
entry["description"] = QString::fromLatin1(id.desc);
|
||||
iface.append(entry);
|
||||
}
|
||||
return iface;
|
||||
}
|
||||
|
||||
QJsonArray ModuleProxy::getPluginMethods()
|
||||
|
||||
Reference in New Issue
Block a user