core+qt+net: fix listInstances session-stamping and model-level attachActionLog forwarding - #122
Merged
Yaraslaut merged 3 commits intoAug 17, 2026
Conversation
…ctionLog RemoteServer::LogProvider's attach path (attachLogIfConfigured) only ever populated the type-erased holder's own _actionLog/_contextKey, used by recordIfAttached's auto-append -- never a model instance's own state. A model that keeps its own model-level IActionLog reference to read its history back later (e.g. an activity-stream view over entries(entityKey)) had no way to receive the same log a registry-constructed, remote/keyed attach populates the holder with, since such an instance is always default-constructed and never otherwise touched. Adds IModelHolder::onActionLogAttached, a protected virtual hook (no-op default) that attachActionLog now calls before storing its own state. ModelHolder<Model> overrides it to forward to Model::attachActionLog(log, contextKey) when Model structurally satisfies the new ModelLevelActionLogAttachable concept -- the same "detect the hook structurally, forward only if present" shape onBackendChanged()/ BackendChangedMixin already use, so a model with no attachActionLog of its own is entirely unaffected. Updates docs/spec/core/registry.md and docs/spec/journal/journal.md to document the new hook. Verified with a standalone probe program exercising ModelHolder<Model> in isolation, and end-to-end in the kanban App bootstrap (next commit) where a registry-constructed BoardModel's GetActivity now sees entries the same dispatch produced. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… the session
Both classes' listInstances() built the wire envelope via
morph::wire::makeInstances(typeId) and sent it directly, unlike every
other envelope-building call site in the same classes (register, attach,
assign, execute, and deregister all set env.session before sending).
RemoteServer's instances handler authorizes with
IAuthorizer::authorize(env.session, typeId, {}) -- with no session on the
envelope, a SigningAuthorizer-derived authorizer (or any authorizer that
actually inspects the session) rejects the call as unauthorized, even on
a connection that already completed a correctly-authenticated execute().
Every existing rung's instances() coverage used an AllowAllAuthorizer-
derived authorizer (or ran Local/in-process, where authorizeInstance
never runs at all per docs/spec/security.md), so authorize() was always
permissive regardless of session and this path went unexercised. Fixed
by stamping env.session from currentSession() (SocketBackend) / _session
(QtWebSocketBackend) before sending, matching every sibling call site.
Found while adding a rung's first Socket-mode instances() test against a
SigningAuthorizer-derived authorizer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Every existing attachActionLog test used ALModel, which has no model-level attachActionLog of its own -- so all of them exercised only the ModelLevelActionLogAttachable<Model> == false arm of ModelHolder<Model>:: onActionLogAttached's `if constexpr`. The == true arm (the actual forwarding mechanism this hook exists for) had no coverage at all on this branch, since the only model in the tree that declares a matching attachActionLog is kanban::BoardModel, which lives on a separate branch. Adds ALLoggingModel, a minimal model declaring attachActionLog matching ModelLevelActionLogAttachable's exact shape, plus two tests: - The forwarding case: attachActionLog on the holder reaches the wrapped model's own attachActionLog with the same log/contextKey, exactly once, without disturbing the holder's own hasActionLog/recordIfAttached state. - The no-op case (ALModel): attachActionLog still succeeds and populates the holder's own state when the wrapped model has no attachActionLog of its own -- confirming the hook's default body runs harmlessly for the overwhelming majority of models that don't opt in. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This was referenced Aug 17, 2026
Closed
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two small, independently-scoped framework bugs, discovered and fixed while implementing the kanban rung (rung 4 of the application ladder — see the companion PR), pulled out here as their own PR since they're morph library fixes, not rung-specific work.
Both are minimal, targeted diffs against
master— no ladder/example code touched.Fix 1 —
QtWebSocketBackend/SocketBackend::listInstancesnever stamped the sessionlistInstances()in both classes built the wire envelope viamorph::wire::makeInstances(typeId)and sent it directly, unlike every other envelope-building call site in the same classes —register,attach,assign,execute, andderegisterall setenv.sessionbefore sending;listInstanceswas the one exception.RemoteServer's instances handler authorizes withIAuthorizer::authorize(env.session, typeId, {}). With no session on the envelope, aSigningAuthorizer-derived authorizer (or any authorizer that actually inspects the session) rejects the call as unauthorized — even on a connection that already completed a correctly-authenticatedexecute().Every existing rung's
instances()coverage used anAllowAllAuthorizer-derived authorizer (or ran Local/in-process, whereauthorizeInstancenever runs at all perdocs/spec/security.md), soauthorize()was always permissive regardless of session, and this path went unexercised until kanban became the first rung to combine aSigningAuthorizerwith a Socket-modeinstances()call.Fix: stamp
env.sessionfromcurrentSession()(SocketBackend) /_session(QtWebSocketBackend) before sending, matching every sibling call site. One line added per file.Filed as morph#113.
Fix 2 —
IModelHolder::attachActionLognever forwarded to a model-levelattachActionLogRemoteServer::LogProvider's attach path (attachLogIfConfigured) only ever populated the type-erased holder's own_actionLog/_contextKey, used byrecordIfAttached's auto-append — never a model instance's own state. A model that keeps its own model-levelIActionLogreference to read its history back later (e.g. an activity-stream view overentries(entityKey)) had no way to receive the same log a registry-constructed, remote/keyed attach populates the holder with, since such an instance is always default-constructed and never otherwise touched.Fix: adds
IModelHolder::onActionLogAttached, a protected virtual hook (no-op default) thatattachActionLognow calls before storing its own state.ModelHolder<Model>overrides it to forward toModel::attachActionLog(log, contextKey)whenModelstructurally satisfies a newModelLevelActionLogAttachableconcept — the same "detect the hook structurally, forward only if present" shapeonBackendChanged()/BackendChangedMixinalready use, so a model with noattachActionLogof its own is entirely unaffected.Updates
docs/spec/core/registry.mdanddocs/spec/journal/journal.mdto document the new hook.Filed as morph#114.
Verification
Both bugs were caught and fixed while building kanban's
BoardModel/KanbanAuthorizer(kanban is the first rung to combine aSigningAuthorizerwith a Socket-modeinstances()call, and the first rung to need a model-level activity-stream view over its own journal). See the companion kanban PR for the integration tests that surfaced each bug.For this standalone PR: verified the
ModelLevelActionLogAttachableconcept is structurally satisfied by exactly one type in the current tree (kanban'sBoardModel, added in the companion PR — not yet present onmaster), so onmasteralone the newif constexprbranch is dead code and theonActionLogAttachedhook's default no-op body runs unconditionally for every existing model — a pure no-op addition until a model opts in.Built and ran the full
morph_testssuite against this branch (based onmaster, no other changes): 10,062 assertions in 1,054 test cases, all passing.Scope note
MORPH_BUILD_NET(which buildsmorph::net::SocketBackend) is a no-op on Windows per the project's own CMake warning —morph::netis POSIX-only today (Winsock2 support is documented future work) — sosocket_backend.hpp's half of Fix 1 wasn't exercised by this build. It's a one-line, source-identical mirror of theQtWebSocketBackendfix in the same file's siblinglistInstancesimplementation, reviewed by inspection.