From 5b94a5f879d53f107ecb5981951cfabc51eb9888 Mon Sep 17 00:00:00 2001 From: Mauve Signweaver Date: Wed, 8 Oct 2025 18:41:42 -0400 Subject: [PATCH 1/3] fix: Blocked members only expect auth core for initial sync --- src/mapeo-manager.js | 6 ++++-- src/roles.js | 2 +- test-e2e/sync.js | 15 ++++++++------- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/mapeo-manager.js b/src/mapeo-manager.js index a4bc8d42..957357cd 100644 --- a/src/mapeo-manager.js +++ b/src/mapeo-manager.js @@ -47,7 +47,7 @@ import { getFastifyServerAddress } from './fastify-plugins/utils.js' import { LocalPeers } from './local-peers.js' import { InviteApi } from './invite/invite-api.js' import { LocalDiscovery } from './discovery/local-discovery.js' -import { Roles } from './roles.js' +import { Roles, BLOCKED_ROLE } from './roles.js' import { Logger } from './logger.js' import { kSyncState, @@ -792,6 +792,7 @@ export class MapeoManager extends TypedEmitter { // in the config store - defining the name of the project. // TODO: Enforce adding a project name in the invite method const isConfigSynced = configState.want === 0 && configState.have > 0 + if (ownRole === BLOCKED_ROLE && isAuthSynced) return true if ( isRoleSynced && isProjectSettingsSynced && @@ -803,6 +804,7 @@ export class MapeoManager extends TypedEmitter { this.#l.log( 'Pending initial sync: role %s, projectSettings %o, auth %o, config %o', isRoleSynced, + isProjectSettingsSynced, isAuthSynced, isConfigSynced ) @@ -816,7 +818,7 @@ export class MapeoManager extends TypedEmitter { return } project.$sync[kSyncState].off('state', onSyncState) - resolve(this.#waitForInitialSync(project, { timeoutMs })) + this.#waitForInitialSync(project, { timeoutMs }).then(resolve, reject) } const onTimeout = () => { project.$sync[kSyncState].off('state', onSyncState) diff --git a/src/roles.js b/src/roles.js index 596f8a45..fd72d33c 100644 --- a/src/roles.js +++ b/src/roles.js @@ -102,7 +102,7 @@ export const CREATOR_ROLE = { /** * @type {Role} */ -const BLOCKED_ROLE = { +export const BLOCKED_ROLE = { roleId: BLOCKED_ROLE_ID, name: 'Blocked', docs: mapObject(currentSchemaVersions, (key) => { diff --git a/test-e2e/sync.js b/test-e2e/sync.js index f696792b..0aa95872 100644 --- a/test-e2e/sync.js +++ b/test-e2e/sync.js @@ -900,6 +900,8 @@ test('no sync capabilities === no namespaces sync apart from auth', async (t) => const [invitor, invitee, blocked] = managers const disconnect1 = connectPeers(managers) + t.after(() => disconnect1()) + const projectId = await invitor.createProject({ name: 'Mapeo' }) await invite({ @@ -919,6 +921,8 @@ test('no sync capabilities === no namespaces sync apart from auth', async (t) => managers.map((m) => m.getProject(projectId)) ) + t.after(() => Promise.all(projects.map((p) => p.close()))) + const [invitorProject, inviteeProject] = projects assert.equal( @@ -955,18 +959,15 @@ test('no sync capabilities === no namespaces sync apart from auth', async (t) => assert.equal(blockedState.data.localState.have, 0) // no data docs synced for (const ns of NAMESPACES) { - assert.equal(invitorState[ns].coreCount, 3, ns) - assert.equal(inviteeState[ns].coreCount, 3, ns) - assert.equal(blockedState[ns].coreCount, 3, ns) + assert.equal(invitorState[ns].coreCount, 3, `invitor got cores for ${ns}`) + assert.equal(inviteeState[ns].coreCount, 3, `invitee got cores for ${ns}`) + assert.equal(blockedState[ns].coreCount, 3, `blocked got cores for ${ns}`) assert.deepEqual( invitorState[ns].localState, inviteeState[ns].localState, - ns + `invitor/invitee have same local state for ${ns}` ) } - - await disconnect1() - await Promise.all(projects.map((p) => p.close())) }) test('Sync state emitted when starting and stopping sync', async function (t) { From aa8be57b875ccbc356719337918824bf5d83567c Mon Sep 17 00:00:00 2001 From: Mauve Signweaver Date: Mon, 13 Oct 2025 11:15:47 -0400 Subject: [PATCH 2/3] chore: More robust blocked check for initial sync --- src/mapeo-manager.js | 9 ++++++--- src/roles.js | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/mapeo-manager.js b/src/mapeo-manager.js index 957357cd..08af3e33 100644 --- a/src/mapeo-manager.js +++ b/src/mapeo-manager.js @@ -47,7 +47,7 @@ import { getFastifyServerAddress } from './fastify-plugins/utils.js' import { LocalPeers } from './local-peers.js' import { InviteApi } from './invite/invite-api.js' import { LocalDiscovery } from './discovery/local-discovery.js' -import { Roles, BLOCKED_ROLE } from './roles.js' +import { Roles } from './roles.js' import { Logger } from './logger.js' import { kSyncState, @@ -792,7 +792,7 @@ export class MapeoManager extends TypedEmitter { // in the config store - defining the name of the project. // TODO: Enforce adding a project name in the invite method const isConfigSynced = configState.want === 0 && configState.have > 0 - if (ownRole === BLOCKED_ROLE && isAuthSynced) return true + if (ownRole.sync.config === 'blocked' && isAuthSynced) return true if ( isRoleSynced && isProjectSettingsSynced && @@ -813,7 +813,10 @@ export class MapeoManager extends TypedEmitter { /** @param {import('./sync/sync-state.js').State} syncState */ const onSyncState = (syncState) => { clearTimeout(timeoutId) - if (syncState.auth.dataToSync || syncState.config.dataToSync) { + if ( + syncState.auth.dataToSync || + (syncState.config.dataToSync && ownRole.sync.config === 'allowed') + ) { timeoutId = setTimeout(onTimeout, timeoutMs) return } diff --git a/src/roles.js b/src/roles.js index fd72d33c..596f8a45 100644 --- a/src/roles.js +++ b/src/roles.js @@ -102,7 +102,7 @@ export const CREATOR_ROLE = { /** * @type {Role} */ -export const BLOCKED_ROLE = { +const BLOCKED_ROLE = { roleId: BLOCKED_ROLE_ID, name: 'Blocked', docs: mapObject(currentSchemaVersions, (key) => { From f68e9f2e3f7b2d333d4513d5ba327c9fb4a620c9 Mon Sep 17 00:00:00 2001 From: Mauve Signweaver Date: Tue, 14 Oct 2025 12:43:24 -0400 Subject: [PATCH 3/3] fix: role must sync before config block check --- src/mapeo-manager.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mapeo-manager.js b/src/mapeo-manager.js index 08af3e33..4d02c247 100644 --- a/src/mapeo-manager.js +++ b/src/mapeo-manager.js @@ -792,7 +792,9 @@ export class MapeoManager extends TypedEmitter { // in the config store - defining the name of the project. // TODO: Enforce adding a project name in the invite method const isConfigSynced = configState.want === 0 && configState.have > 0 - if (ownRole.sync.config === 'blocked' && isAuthSynced) return true + if (isRoleSynced && ownRole.sync.config === 'blocked' && isAuthSynced) { + return true + } if ( isRoleSynced && isProjectSettingsSynced &&