fix: per-player permission sync — push + member-mapping UI + match warnings + DM-only clarity#67
Merged
Merged
Conversation
…-error surfacing
Closes the per-player permission gaps from the 2026-06-21 permission-sync
audit. Fail-CLOSED posture preserved: ambiguity always resolves toward LESS
player visibility.
1. Per-user grant PUSH (Foundry→Chronicle). journal-sync _buildPermissionGrants
(new pure helper) reverse-maps each Foundry per-user ownership entry to a
Chronicle {subject_type:'user', subject_id, permission} grant via
getChronicleUserId (level >= OWNER -> edit, else view). _pushPermissions
sends visibility:'custom' only when real user grants exist; unmapped Foundry
users are skipped (never over-shared) and surfaced.
2. Manual mapping UI + match-error surfacing (operator must-have). New dashboard
Members tab lists every Chronicle member with a Foundry-user dropdown +
matched/UNMATCHED badge, persisting via setUserMappings. On connect,
fetchAndCacheMembers now emits a visible ui.notifications.warn + dashboard
warning listing every member that could NOT be auto-matched (previously
silently skipped). memberKey() is the single source of truth for the
member->mapping key.
3. Surface the other silent permission-sync failures (audit section 3): dropped
unmapped grant in _buildOwnership, fail-closed-to-NONE permissions-API error,
per-user push failures, and members-fetch failure now log a dashboard
warning/error + notify where user-facing.
4. Clarify the perception gap (audit section 1A): noteDmOnlyHidden /
getDmOnlyHiddenCount track private entities that synced but landed
hidden-from-players; Status tab shows the count so it isn't mistaken for
"didn't sync."
5. Consume tag_grants + public in _buildOwnership: a public grant maps to an
OBSERVER/OWNER default (never lowers a higher grant); tag_grants are surfaced
as a warning since Foundry can't expand them to users.
Tests: tools/test-permission-sync.mjs (push grant-building, memberKey, unmatched
warning, member-row view-model) + new cases in tools/test-build-ownership.mjs.
Full suite green (485 tests).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y14Y6VXb7iB2xcNrYNDJa5
Merged
2 tasks
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.
Cites: 2026-05-21-core-tenets §T-B1 (fail-closed visibility);
reports/coordinator/2026-06-21-permission-sync-audit.md§1A/§2/§3/§5.Security implication: all new paths fail CLOSED — unmapped users are dropped on both push and pull (never over-shares), and every mismatch is now surfaced instead of silently widening access.
What this changes
Closes the per-player permission-sync gaps. Pairs with Chronicle PR #488 (which makes permission changes broadcast) — both are needed for a Chronicle custom-permission change to fully reflect in Foundry.
getChronicleUserId), not just a coarse role grant;visibility:'custom'only when real user grants exist; unmapped users skipped (never over-share).setUserMappings); on connect, a visible warning (ui.notifications+ dashboard log) lists every member that couldn't be auto-matched — no more silent drops.public/tag_grantsin_buildOwnership(only ever widens toward intent; fail-closed preserved).Test plan
node --testfull suite: 485 pass, 0 fail (+24 new tests)🤖 Generated with Claude Code
https://claude.ai/code/session_01Y14Y6VXb7iB2xcNrYNDJa5
Generated by Claude Code