Skip to content

IModelHolder had no way for a registry-constructed model to receive its own journal attachment #114

Description

@Yaraslaut

Problem

`IModelHolder`'s `attachActionLog`/`recordIfAttached`/`hasActionLog` operate
entirely on the holder wrapper — never on the wrapped model instance. This means
a model that itself wants to read back its own journal (e.g. to derive an
in-model feature like an activity stream from `IActionLog::entries(entityKey)`)
has no way to get at the same `IActionLog` the holder's own auto-append writes
entries into: `ModelFactory::create()` auto-attaches the process-wide
journal to the holder on every registry-constructed instance, and never calls
any model-level method.

Concretely: a registry-constructed model (the kind `RemoteServer`/`App` actually
dispatches through in production) has no mechanism to keep its own
`std::shared_ptr` in sync with the holder's, so any model-level
feature reading its own journal would see it as always-empty in real deployment,
even though the exact same code works in a unit test that constructs the model
directly and calls a hand-rolled `attachActionLog` itself.

This surfaced building rung 4 (kanban) of the application ladder: `BoardModel`'s
activity-stream feature (design: derive from the framework journal via
`IActionLog::entries(entityKey)`, not a parallel table) needed exactly this, and
turned out to be the first model in the codebase attempting it.

Fix (already applied on the in-progress `ladder-kanban-impl` branch)

Added a `ModelLevelActionLogAttachable` concept (`include/morph/core/model.hpp`):

template <typename M>
concept ModelLevelActionLogAttachable = requires(M& model, std::shared_ptr<::morph::journal::IActionLog> log,
                                                  std::string key) {
    { model.attachActionLog(log, key) } -> std::same_as<void>;
};

`ModelHolder::onActionLogAttached` forwards to the wrapped model's own
`attachActionLog(log, contextKey)` iff the model declares one matching this
concept, gated by `if constexpr` — a no-op, unchanged compile path for every
model that doesn't declare one. This mirrors an already-established pattern in
the same file (`BackendChangedNotifiable`/`BackendChangedMixin`'s
`onBackendChanged()` hook), not a new idiom.

Verified via a codebase-wide grep that `kanban::BoardModel` is currently the
only model type declaring `attachActionLog` — every other model (bank,
bookmarks, pastebin, polls, and kanban's own `ProjectAdminModel`/`AuthModel`)
is structurally untouched by this change; the `if constexpr` branch simply
never instantiates for them.

An end-to-end test (dispatching through a real `wire::Envelope`/`RemoteServer`
path, not a hand-constructed model instance) confirms a registry-constructed
`BoardModel` now shares the same `IActionLog` the holder's own auto-append
writes to.

Ask

Backport this small, additive, opt-in framework hook to `master` independently
of kanban's own PR — it's a general-purpose seam any future model needing to
read back its own attached journal can use, not kanban-specific.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions