session: add Principal, readable authorization state outside a dispatch - #34
Merged
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
session::current() (Context::principal) only exists during a dispatch, so UI code -- a button's enabled state, a menu item's visibility -- had no way to ask "who is signed in and what may they do?" without attempting the action and catching the refusal. Applications ended up maintaining a second, parallel notion of the signed-in user's permissions for the UI, with no guarantee it agreed with the one morph verifies server-side. Add morph::session::Principal (id, roles, claims, hasRole()) and Bridge::setPrincipal/currentPrincipal: an application installs it once, typically right after a successful login dispatch from data the server actually returned, and UI code reads it back outside any dispatch. Scoped to the specific Bridge instance -- not a process-wide global -- alongside the existing setDefaultSession/defaultSession, each guarded by its own mutex. Purely a client-side convenience: no wire representation, and it never substitutes for server-side authorization, which still runs on every dispatch via IAuthorizer regardless of what currentPrincipal() says. Closes #24 Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
Principal{.id = ..., .roles = ...} (and similar partial forms) tripped
-Wmissing-designated-field-initializers (clang) / -Wmissing-field-initializers
(gcc) under this project's -Werror build, since neither warning is in the
opt-out list in apply_warnings(). This broke every real build/test CI job
(15 of them) plus clang-tidy-diff, which all share the same compile step.
Name every field (id/roles/claims) in each partial designated
initializer; Principal{} (all-default) is unaffected since it names
zero fields, which both compilers accept.
Signed-off-by: Yaraslau Tamashevich <yaraslau.tamashevich@gmail.com>
Yaraslaut
force-pushed
the
feature/24-readable-principal
branch
from
August 4, 2026 16:35
f53e9c6 to
899c275
Compare
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
session::current()(Context::principal) only exists during a dispatch, so UI code had no way to ask "who is signed in and what may they do?" without attempting the action and catching the refusal — issue No readable authorization state outside a dispatch (UI cannot gate on roles) #24's exact complaint.Bridgeinstance, not a global (confirmed with the repo owner before implementing) — the same object that already holds the defaultsession::ContextviasetDefaultSession/defaultSession.morph::session::Principal(id,roles,claims,hasRole()) andBridge::setPrincipal(principal)/Bridge::currentPrincipal(), guarded by their own mutex (separate from the session mutex, since UI code is expected to read it far more often).Contextor dispatch. Every dispatch is still authorized server-side viaIAuthorizerregardless of whatcurrentPrincipal()says — this only shapes what the UI offers.docs/spec/session/session.md(newPrincipalsection, API reference, design-decision row, and a note on thecurrent()limitation this closes) anddocs/spec/core/bridge.md(newsetPrincipal/currentPrincipalAPI rows).Test plan
tests/test_principal.cpp:Principal/hasRoleunit tests;Bridge::setPrincipal/currentPrincipalround-trip (id/roles/claims); readable with no dispatch in flight at all (the exact gap reported); clearing via a default-constructedPrincipal{}(sign-out); independence across two separateBridgeinstances../build/tests/morph_tests— all 818 test cases / 8304 assertions pass.Closes #24
🤖 Generated with Claude Code