The guard prevented ensure_default_roles from working in the enable
command — it checked the cascading state event which hasn't been
written yet at that point. Callers should gate this themselves.
Remove the else branch that stripped power levels from users not managed
by any space role. This was destroying manually-set PLs for users who
happened to be in space-managed rooms but had no space roles. Space
roles should only SET power levels for managed users, never remove
entries for non-managed users.
Add handle_space_member_leave to kick users from child rooms when they
leave or are banned from a Space. Handle both Join and Leave/Ban
membership transitions in on_pdu_appended dispatch.
Fix enable command to create default roles before sending the cascading
enable event, preventing enforcement from running against empty roles.
- Make cache fields private, add get_user_roles_in_space and
get_room_requirements_in_space accessor methods for admin layer
- Add flush_space_from_cache and call it when cascading is disabled
for a space (prevents stale enforcement data)
- Fix err!(Err("...")) -> err!("...") (redundant variant wrapper)
- Fix variable naming: sender -> server_user in ensure_default_roles
- Fix UFCS turbofish in validate_pl_change to simpler .as_str()
- Import Semaphore instead of inline tokio::sync::Semaphore path
- Add power_level bounds validation in add command (Matrix Int range)
- Add room-is-child-of-space validation in require command
- Handle SPACE_CASCADING_EVENT_TYPE in enforcement dispatch to flush
cache when a space is disabled
Check server.running() at the start of each spawned task to avoid
doing enforcement work during shutdown, matching the codebase's
established pattern for short-lived spawned tasks.
- Fix pending_enforcement cleanup: wrap enforcement body in async block
so removal always runs even on semaphore error or early return
- Fix check_join_allowed: remove is_enabled() guard that blocked
per-space overrides when global flag is false (get_parent_spaces
already filters by is_enabled_for_space)
- Fix kick/join asymmetry: kick_unqualified_from_rooms now checks all
parent spaces before kicking, matching check_join_allowed's OR logic
- Fix lock ordering: validate_pl_change now acquires roles before
user_roles, matching get_user_power_level's order
- Fix ensure_default_roles TOCTOU: move existence check inside state
lock to prevent concurrent duplicate writes
Move all space roles logic out of append.rs, build.rs, and join.rs
into service methods (on_pdu_appended, validate_pl_change,
check_join_allowed). Existing files now have single-line call sites
instead of inline logic. Extract flush_caches helper to deduplicate
cache clearing.
Extract StateEventType constructor helpers to avoid repeated
to_owned() allocations at each call site. Extract
sync_power_levels_for_children to consolidate 3 identical loops.
Add com.continuwuity.space.cascading state event for per-Space override
of the server-wide space_permission_cascading config. Add enable/disable/
status admin commands. Strip superfluous comments throughout.
Run cargo +nightly fmt, add towncrier news fragment, remove plan
documents that served their purpose during development.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- PL omission check now allows omitting a space-managed user if the
proposed users_default equals their space-granted PL
- Semaphore acquire errors cause early return instead of silent proceed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix doc comment referencing room_to_space instead of space_to_rooms
- Add space_to_rooms forward index to design doc index table
- Use Err! consistently for validation errors in admin commands
- Rename test to follow deserialize_ prefix convention
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Convert all log statements in space roles code to use structured
key-value fields instead of string interpolation, matching the project
code style. Fix import ordering (serde_json moved after conduwuit_core),
move a misplaced `use futures::StreamExt` from function body to
file-level imports, add lock ordering comments to prevent deadlocks,
fix populate_space to acquire locks in the same order as
handle_space_child_change, add diagnostic debug_warn before PL
rejection errors, and document the nested cascade limitation on
get_parent_spaces.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix all clippy warnings in space roles files: dangerous `as` casts,
`to_string()` on &str, format string inlining, items-after-statements,
needless borrows, large futures, semicolons outside blocks, and
let-else patterns.
Extract `compute_user_power_level` and `roles_satisfy_requirements` as
pure free functions so the core logic can be unit-tested without async
service dependencies. Update all tests in tests.rs and
integration_tests.rs to call the real extracted functions instead of
reimplementing the logic inline.
Add negative deserialization tests for RoleDefinition,
SpaceRoleMemberEventContent, and SpaceRoleRoomEventContent. Improve
doc comments on handle_* methods and add module-level documentation to
cache_tests.rs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add per-space dedup set to prevent concurrent enforcement tasks from
competing when multiple role events fire rapidly for the same space
- Add space_roles_cache_capacity config (default 1000) to bound cache
growth, clearing all caches when exceeded
- Add PartialEq/Eq derives to all space role event content types
- Skip server user in auto_join_qualifying_rooms and handle_space_member_join
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract event type string literals as constants in space_roles.rs and
replace all occurrences across service and admin code. Add a forward
index (space_to_rooms) for O(1) child room lookups instead of scanning
the reverse index. Introduce resolve_space! macro to deduplicate the
repeated enabled-check + alias-resolve + space-type-guard pattern in
all 9 admin command handlers. Flatten deeply nested if-let chains in
append.rs using let-chains syntax.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove redundant StdHashSet import alias in cache_tests.rs
- Add type alias SpaceEnforcementData for readability in build.rs
- Fix formatting of for-loop closing brace in PL check
- Move BTreeMap and RoleDefinition imports to file-level in build.rs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Critical fixes:
- handle_space_child_change now reads the actual m.space.child state event
and checks if via is empty; removes child from index on removal instead
of unconditionally adding
- Server user is exempted from PL rejection guard so sync_power_levels
can function without being blocked by its own protection
- PL rejection now also checks that space-managed users aren't omitted
from proposed power level events
Important fixes:
- room_to_space changed from 1:1 to 1:many (HashMap<RoomId, HashSet<RoomId>>)
so a room can belong to multiple parent spaces; get_parent_space renamed
to get_parent_spaces; join gating checks all parents (qualify in any)
- All custom event types renamed from m.space.* to com.continuwuity.space.*
to avoid squatting on the Matrix namespace
- Cache cleanup on child removal from space
- Added tokio Semaphore (capacity 4) to limit concurrent enforcement tasks
- Server user membership checked before enforcement in auto_join, kick,
and sync_power_levels to avoid noisy errors
Suggestions:
- Replaced expect() calls with proper error propagation via map_err/?
- Fixed indentation in timeline/mod.rs line 116
- handle_space_child_change now directly joins users to the specific new
child room instead of scanning all children via auto_join_qualifying_rooms
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Gate memory_usage() and clear_cache() with is_enabled()
- Gate populate_space() and get_parent_space() as defense-in-depth
- All admin commands now refuse when feature is disabled with
a clear message pointing to the config option
- Prefix memory labels with space_ for disambiguation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add spawn_enforcement methods (handle_state_event_change,
handle_space_child_change, handle_space_member_join) that run
enforcement as background tasks to avoid recursive Send issues
- Expand append_pdu hook to trigger enforcement on role events,
space child changes, and space member joins
- Fix deadlock risk in get_user_power_level and user_qualifies_for_room
by dropping read guards before acquiring new ones
- Batch room_to_space writes in populate_space with a single write lock
- Add space type validation to all admin commands
- Fix PL rejection check to reject any change (!=) not just lowering (<)
- Fix sync_power_levels to also lower PLs for users who lost their roles
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checks proposed m.room.power_levels events against Space-granted power
levels. Rejects if any user's proposed PL is below their Space role PL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ensure_default_roles() to check if a Space has m.space.roles state
event and create default admin/mod roles if missing. Add worker() to
rebuild the space roles cache on startup by iterating all rooms and
populating cache for spaces.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Updates the space roles cache when m.space.roles, m.space.role.member,
or m.space.role.room state events are appended. Adds roles service as
a dependency of the timeline service.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- sync_power_levels(): Overrides child room PLs with Space role PLs
- auto_join_qualifying_rooms(): Joins user to all rooms they qualify for
- kick_unqualified_from_rooms(): Kicks user from rooms they no longer qualify for
- Adds globals dep for server_user access
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checks if user has required Space roles before allowing join to a
child room. Runs after antispam checks, before the actual join path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds is_enabled(), populate_space(), get_user_power_level(),
user_qualifies_for_room(), and get_parent_space() methods.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Create rooms::roles::Service with in-memory caches for role definitions,
user-role assignments, room requirements, and room-to-space mappings.
Register the service in the service stack alongside other room services.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Define serde content types for m.space.roles, m.space.role.member,
and m.space.role.room custom state events used by space permission
cascading.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
15-task plan covering config flag, custom event types, service layer,
cache, enforcement hooks, admin commands, and testing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Covers power level cascading from Spaces to child rooms, role-based
room access control, continuous enforcement, and admin room commands.
Feature will be behind a server-wide config flag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>