4 Commits
Author SHA1 Message Date
Dario Gabriel Lipicar 9b8cf6e47e feat(shell): ship main_ui as a plugin that links no logos runtime
Measured on the built artefact: main_ui.dylib DEFINES zero and IMPORTS zero of
TokenManager, StoreRegistry, LogosAPI, LogosAPIClient and logos_core_*, out of
3410 symbols read. It links Qt and nothing else from this workspace, which
nix/symbol-gate.nix enforces across the in-process image set.

Getting there needed the last non-Qt types off the boundary: the model
properties cross as QAbstractItemModel*, catalogInstallStageChanged carries an
int rather than InstallStage::Value, and the two prebuilt AppsFilterProxy
instances are declared in QML instead of owned by MainUIBackend. That last one
removes a real inversion -- PackageCoordinator called setRequiredPackages() on
a proxy the host held a pointer to; it now emits requiredPackagesResolved() and
QML binds to the republished property.

Window resolves the plugin, qobject_casts it to IShellView, checks
hostAbiVersion() against IShellHost_abi, and calls createShell(IShellHost*).
No error-label fallback widget: that degraded to something that looked like a
working app with an empty window.

Filter proxies read role constants off host-side models and InstallEnums is
used by nine host files, so app/interfaces/ gains the contract headers both
sides compile against -- the models inherit the role structs, leaving every
AppsModel::NameRole call site unchanged. The plugin's include path is
app/interfaces only, so including a host header does not compile.

Four things only running it finds:
  * Logos::DesignSystem may be linked by exactly ONE image -- both linked it
    and the app aborted with "Cannot add multiple registrations for
    Logos.Icons"; QML module registration is process-global
  * qmltyperegistrar emits no #include for a SOURCES header given as an
    absolute path outside the project
  * each qt_add_qml_module is its own target and inherits no include dirs
  * AUTOMOC pairs header<->cpp by same-basename-same-DIRECTORY, which the
    split breaks

tst_AppManagerView.qml grows five tests for the QML binding, checked with a
negative control: breaking one assertion fails qml-tests, so they run.
2026-08-22 16:42:13 -03:00
Dario Gabriel Lipicar 9c3d023062 feat(shell): put the UI shell behind IShellHost, and order its shutdown
The shell held the host's objects: MainContainer owned MainUIBackend, took a
LogosAPI* and a QtLogosCore*, and connected to backend signals by concrete
type. Nothing stopped it minting identities or reading the token store.

Three headers in app/interfaces/, on the include path both targets share, so
there is exactly one copy and source drift is impossible:

  * IShellHost     -- 8 operations the shell may perform
  * IShellObserver -- 5 notifications the host may deliver
  * IShellView     -- how the host builds and tears down the shell

Only QObject*, QWidget* and Qt value types cross. MainContainer holds one
IShellHost* and nothing else; QML reaches the backend as an opaque QObject*
via backendObject(), resolved through the metaobject, so no host C++ type has
to be nameable by the shell.

Ownership inverts: Window owns MainUIBackend, ShellHostAdapter and
MainShellView; the shell borrows. Teardown stops being a consequence of
construction order and becomes a stated contract:

  1. beginShutdown() unmounts in-process UI plugin widgets WHILE the shell's
     tree is intact -- they are docked inside it
  2. destroyShell() detaches the observer, then deletes the shell
  3. the backend goes, tearing down Package -> UIPlugin -> Core
  4. main() destroys the core facade

Every observer forward is null-guarded: PluginLoader dispatches through
QTimer::singleShot(0, ...) and a 30s ViewModuleHost timeout, so a callback can
land after teardown starts, and QPointer cannot help -- IShellObserver is not
a QObject. UIPluginManager's plugin-widget maps become QPointer for the
mirror-image reason: those widgets are docked inside the shell, so its Qt
parent can destroy them without going through unloadUiModule.

IComponent is untouched and still serves the third-party legacy widget
plugins PluginLoader loads.
2026-08-22 16:42:13 -03:00
Dario Gabriel Lipicar 13665a5e34 feat(host-core): adopt the SDK core facade, and delete both C ABI mirrors
CoreModuleManager and main.cpp each carried a hand-written `extern "C"` mirror
of liblogos' core-management ABI -- two blocks declaring the same symbols in
one image, an ODR hazard with no diagnostic. Both are replaced by
logos::qt::QtLogosCore over logos::host::LogosCore, which owns the
char*/char** marshalling, the `delete[]`-not-`free()` rule, and the pre-start
ordering constraint -- the last as constructor arguments, so the illegal order
stops being expressible.

Four things about the stats path that fail INVISIBLY, all preserved:
  * "cpu"/"memory" stay 1-decimal STRINGS -- QML and ModuleInstanceModel bind
    those names, so the facade's cpuPercent/memoryMb spelling stops here
  * allStats() is called ONCE per tick; each moduleStats(name) repeats the
    full C call and parse, and the snapshot walks every known module
  * the three-way back-compat fallbacks are kept for older runtimes
  * memoryMb (double), never memoryBytes

The cpp-sdk lock moves for that last point: the pin predated the rename, and
that pair does not compile -- qt-sdk's header reads s.memoryMb while the
pinned cpp-sdk struct still called it memoryBytes.

Also drops a QTimer in main.cpp that was started and stopped with no
connect() at all.
2026-08-22 16:42:13 -03:00
Dario Gabriel Lipicar 7851bf9f46 feat(host): per-plugin identities, an opt-in access policy, and the source split
Each loaded plugin -- including pure-QML ones -- gets its own LogosAPI identity
rather than sharing the host's, so a plugin's calls are attributable and can be
refused independently. The host-services grant is wired through to
capability_module, and its trust root is guarded on an OUTCOME rather than a
log line.

Inter-module access policy stays OFF by default: enforce mode's derived
deny-by-default gates every ui_qml app's calls to its own backend module,
because UI plugins load out-of-process and are not tracked as dependents in the
core ModuleRegistry. Operators opt in per launch with --access-policy enforce
or LOGOS_ACCESS_POLICY.

Takes the Qt host runtime from logos-plugin-qt rather than logos-qt-sdk, which
keeps only the Qt<->lp seam headers, and moves logos-protocol onto the rev that
split host needs. On Windows logos_core must come LAST on the link line: GNU ld
resolves an archive left to right, so the view runtime's references have to be
undefined already when it reaches the import library.

Separates the two source trees -- app/ is the host, src/ is the UI shell -- and
brings the CI onto setup-nix-cache-action. Merges master.
2026-08-22 16:42:13 -03:00