From 159920bc8447836922c04dfd4ba8a743fca371db Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Fri, 8 May 2026 01:15:13 +0100 Subject: [PATCH] fix: scope synced blocks to workspace, gate unsync on edit permission --- .../extensions/persistence.extension.ts | 15 ++++- .../src/core/page/services/page.service.ts | 12 +++- .../spec/transclusion.controller.spec.ts | 6 +- .../spec/transclusion.service.spec.ts | 25 ++++--- .../transclusion/transclusion.controller.ts | 6 +- .../page/transclusion/transclusion.service.ts | 66 +++++++++++-------- apps/server/src/core/share/share.service.ts | 1 + .../20260501T202258-page-transclusions.ts | 18 +++++ .../page-transclusion-references.repo.ts | 2 + .../page-transclusions.repo.ts | 2 + .../src/database/repos/page/page.repo.ts | 14 +++- apps/server/src/database/types/db.d.ts | 2 + apps/server/src/ee | 2 +- 13 files changed, 122 insertions(+), 49 deletions(-) diff --git a/apps/server/src/collaboration/extensions/persistence.extension.ts b/apps/server/src/collaboration/extensions/persistence.extension.ts index fd20af7a..53bc8b33 100644 --- a/apps/server/src/collaboration/extensions/persistence.extension.ts +++ b/apps/server/src/collaboration/extensions/persistence.extension.ts @@ -165,7 +165,7 @@ export class PersistenceExtension implements Extension { } if (page) { - await this.syncTransclusion(pageId, tiptapJson); + await this.syncTransclusion(pageId, page.workspaceId, tiptapJson); } if (page) { @@ -250,10 +250,15 @@ export class PersistenceExtension implements Extension { */ private async syncTransclusion( pageId: string, + workspaceId: string, tiptapJson: unknown, ): Promise { try { - await this.transclusionService.syncPageTransclusions(pageId, tiptapJson); + await this.transclusionService.syncPageTransclusions( + pageId, + workspaceId, + tiptapJson, + ); } catch (err) { this.logger.error( { err, pageId }, @@ -261,7 +266,11 @@ export class PersistenceExtension implements Extension { ); } try { - await this.transclusionService.syncPageReferences(pageId, tiptapJson); + await this.transclusionService.syncPageReferences( + pageId, + workspaceId, + tiptapJson, + ); } catch (err) { this.logger.error( { err, pageId }, diff --git a/apps/server/src/core/page/services/page.service.ts b/apps/server/src/core/page/services/page.service.ts index 57acfc49..75912ed2 100644 --- a/apps/server/src/core/page/services/page.service.ts +++ b/apps/server/src/core/page/services/page.service.ts @@ -677,7 +677,11 @@ export class PageService { // pages never have prior rows so we can skip the diff and just bulk-insert. try { await this.transclusionService.insertTransclusionsForPages( - insertablePages.map((p) => ({ id: p.id, content: p.content })), + insertablePages.map((p) => ({ + id: p.id, + workspaceId: p.workspaceId, + content: p.content, + })), ); } catch (err) { this.logger.error( @@ -688,7 +692,11 @@ export class PageService { try { await this.transclusionService.insertReferencesForPages( - insertablePages.map((p) => ({ id: p.id, content: p.content })), + insertablePages.map((p) => ({ + id: p.id, + workspaceId: p.workspaceId, + content: p.content, + })), ); } catch (err) { this.logger.error( diff --git a/apps/server/src/core/page/transclusion/spec/transclusion.controller.spec.ts b/apps/server/src/core/page/transclusion/spec/transclusion.controller.spec.ts index ead7dae3..e6a73103 100644 --- a/apps/server/src/core/page/transclusion/spec/transclusion.controller.spec.ts +++ b/apps/server/src/core/page/transclusion/spec/transclusion.controller.spec.ts @@ -25,10 +25,10 @@ describe('TransclusionController.lookup', () => { controller = module.get(TransclusionController); }); - const user = { id: 'u1' } as any; + const user = { id: 'u1', workspaceId: 'w1' } as any; const ref = { sourcePageId: 'p1', transclusionId: 'e1' }; - it('passes the references and viewer id through to the service and returns its result', async () => { + it('passes the references, viewer id and workspace id through to the service and returns its result', async () => { service.lookup.mockResolvedValue({ items: [ { @@ -43,6 +43,6 @@ describe('TransclusionController.lookup', () => { const out = await controller.lookup({ references: [ref] } as any, user); expect(out.items[0]).not.toHaveProperty('status'); expect((out.items[0] as any).content).toEqual({ type: 'doc' }); - expect(service.lookup).toHaveBeenCalledWith([ref], 'u1'); + expect(service.lookup).toHaveBeenCalledWith([ref], 'u1', 'w1'); }); }); diff --git a/apps/server/src/core/page/transclusion/spec/transclusion.service.spec.ts b/apps/server/src/core/page/transclusion/spec/transclusion.service.spec.ts index 15a7ea09..2808dcd2 100644 --- a/apps/server/src/core/page/transclusion/spec/transclusion.service.spec.ts +++ b/apps/server/src/core/page/transclusion/spec/transclusion.service.spec.ts @@ -6,6 +6,7 @@ import { PageRepo } from '@docmost/db/repos/page/page.repo'; import { PagePermissionRepo } from '@docmost/db/repos/page/page-permission.repo'; import { AttachmentRepo } from '@docmost/db/repos/attachment/attachment.repo'; import { StorageService } from '../../../../integrations/storage/storage.service'; +import { PageAccessService } from '../../page-access/page-access.service'; describe('TransclusionService.syncPageTransclusions', () => { let service: TransclusionService; @@ -27,6 +28,7 @@ describe('TransclusionService.syncPageTransclusions', () => { { provide: PagePermissionRepo, useValue: {} }, { provide: AttachmentRepo, useValue: {} }, { provide: StorageService, useValue: {} }, + { provide: PageAccessService, useValue: {} }, ], }).compile(); service = module.get(TransclusionService); @@ -34,6 +36,7 @@ describe('TransclusionService.syncPageTransclusions', () => { }); const pageId = '00000000-0000-0000-0000-000000000001'; + const workspaceId = '00000000-0000-0000-0000-000000000099'; it('inserts new transclusions that did not exist before', async () => { repo.findByPageId.mockResolvedValue([]); @@ -48,7 +51,7 @@ describe('TransclusionService.syncPageTransclusions', () => { ], }; - const result = await service.syncPageTransclusions(pageId, pm); + const result = await service.syncPageTransclusions(pageId, workspaceId, pm); expect(result).toEqual({ inserted: 1, updated: 0, deleted: 0 }); expect(repo.insert).toHaveBeenCalledTimes(1); @@ -91,7 +94,7 @@ describe('TransclusionService.syncPageTransclusions', () => { ], }; - const result = await service.syncPageTransclusions(pageId, pm); + const result = await service.syncPageTransclusions(pageId, workspaceId, pm); expect(result).toEqual({ inserted: 0, updated: 1, deleted: 0 }); expect(repo.update).toHaveBeenCalledWith( @@ -128,7 +131,7 @@ describe('TransclusionService.syncPageTransclusions', () => { ], }; - const result = await service.syncPageTransclusions(pageId, pm); + const result = await service.syncPageTransclusions(pageId, workspaceId, pm); expect(result).toEqual({ inserted: 0, updated: 0, deleted: 0 }); expect(repo.update).not.toHaveBeenCalled(); @@ -147,7 +150,7 @@ describe('TransclusionService.syncPageTransclusions', () => { ]); const pm = { type: 'doc', content: [{ type: 'paragraph' }] }; - const result = await service.syncPageTransclusions(pageId, pm); + const result = await service.syncPageTransclusions(pageId, workspaceId, pm); expect(result).toEqual({ inserted: 0, updated: 0, deleted: 1 }); expect(repo.deleteByPageAndTransclusionIds).toHaveBeenCalledWith( @@ -159,7 +162,7 @@ describe('TransclusionService.syncPageTransclusions', () => { it('handles empty doc → noop', async () => { repo.findByPageId.mockResolvedValue([]); - const result = await service.syncPageTransclusions(pageId, null); + const result = await service.syncPageTransclusions(pageId, workspaceId, null); expect(result).toEqual({ inserted: 0, updated: 0, deleted: 0 }); expect(repo.insert).not.toHaveBeenCalled(); expect(repo.update).not.toHaveBeenCalled(); @@ -187,6 +190,7 @@ describe('TransclusionService.syncPageReferences', () => { { provide: PagePermissionRepo, useValue: {} }, { provide: AttachmentRepo, useValue: {} }, { provide: StorageService, useValue: {} }, + { provide: PageAccessService, useValue: {} }, ], }).compile(); service = module.get(TransclusionService); @@ -194,6 +198,7 @@ describe('TransclusionService.syncPageReferences', () => { }); const referencePageId = '00000000-0000-0000-0000-000000000001'; + const workspaceId = '00000000-0000-0000-0000-000000000099'; it('inserts new loose references, no deletes when none existed', async () => { refRepo.findByReferencePageId.mockResolvedValue([]); @@ -211,17 +216,19 @@ describe('TransclusionService.syncPageReferences', () => { ], }; - const result = await service.syncPageReferences(referencePageId, pm); + const result = await service.syncPageReferences(referencePageId, workspaceId, pm); expect(result).toEqual({ inserted: 2, deleted: 0 }); expect(refRepo.insertMany).toHaveBeenCalledWith( [ { + workspaceId, referencePageId, sourcePageId: 'p1', transclusionId: 'e1', }, { + workspaceId, referencePageId, sourcePageId: 'p2', transclusionId: 'e2', @@ -250,7 +257,7 @@ describe('TransclusionService.syncPageReferences', () => { ], }; - const result = await service.syncPageReferences(referencePageId, pm); + const result = await service.syncPageReferences(referencePageId, workspaceId, pm); expect(result).toEqual({ inserted: 0, deleted: 0 }); expect(refRepo.insertMany).not.toHaveBeenCalled(); @@ -268,7 +275,7 @@ describe('TransclusionService.syncPageReferences', () => { ]); const pm = { type: 'doc', content: [{ type: 'paragraph' }] }; - const result = await service.syncPageReferences(referencePageId, pm); + const result = await service.syncPageReferences(referencePageId, workspaceId, pm); expect(result).toEqual({ inserted: 0, deleted: 1 }); expect(refRepo.deleteByReferenceAndKeys).toHaveBeenCalledWith( @@ -304,7 +311,7 @@ describe('TransclusionService.syncPageReferences', () => { ], }; - const result = await service.syncPageReferences(referencePageId, pm); + const result = await service.syncPageReferences(referencePageId, workspaceId, pm); expect(result).toEqual({ inserted: 0, deleted: 0 }); expect(refRepo.insertMany).not.toHaveBeenCalled(); diff --git a/apps/server/src/core/page/transclusion/transclusion.controller.ts b/apps/server/src/core/page/transclusion/transclusion.controller.ts index d1c19fd9..72b1fea2 100644 --- a/apps/server/src/core/page/transclusion/transclusion.controller.ts +++ b/apps/server/src/core/page/transclusion/transclusion.controller.ts @@ -24,7 +24,8 @@ export class TransclusionController { async lookup(@Body() dto: LookupDto, @AuthUser() user: User) { return this.transclusionService.lookup( dto.references, - user?.id ?? null, + user.id, + user.workspaceId, ); } @@ -38,6 +39,7 @@ export class TransclusionController { sourcePageId: dto.sourcePageId, transclusionId: dto.transclusionId, viewerUserId: user.id, + workspaceId: user.workspaceId, }); } @@ -51,7 +53,7 @@ export class TransclusionController { dto.referencePageId, dto.sourcePageId, dto.transclusionId, - user.id, + user, ); } } diff --git a/apps/server/src/core/page/transclusion/transclusion.service.ts b/apps/server/src/core/page/transclusion/transclusion.service.ts index d6945fe7..d851a984 100644 --- a/apps/server/src/core/page/transclusion/transclusion.service.ts +++ b/apps/server/src/core/page/transclusion/transclusion.service.ts @@ -19,7 +19,8 @@ import { } from './utils/transclusion-prosemirror.util'; import { rewriteAttachmentsForUnsync } from './utils/transclusion-unsync.util'; import { TransclusionLookup } from './transclusion.types'; -import { Page } from '@docmost/db/types/entity.types'; +import { Page, User } from '@docmost/db/types/entity.types'; +import { PageAccessService } from '../page-access/page-access.service'; type ReferencingPageInfo = { id: string; @@ -41,10 +42,12 @@ export class TransclusionService { private readonly pagePermissionRepo: PagePermissionRepo, private readonly attachmentRepo: AttachmentRepo, private readonly storageService: StorageService, + private readonly pageAccessService: PageAccessService, ) {} async syncPageTransclusions( pageId: string, + workspaceId: string, pmJson: unknown, trx?: KyselyTransaction, ): Promise<{ inserted: number; updated: number; deleted: number }> { @@ -63,6 +66,7 @@ export class TransclusionService { if (!prev) { await this.pageTransclusionsRepo.insert( { + workspaceId, pageId, transclusionId: d.transclusionId, content: d.content as any, @@ -102,6 +106,7 @@ export class TransclusionService { async syncPageReferences( referencePageId: string, + workspaceId: string, pmJson: unknown, trx?: KyselyTransaction, ): Promise<{ inserted: number; deleted: number }> { @@ -121,6 +126,7 @@ export class TransclusionService { const toInsert = desired .filter((d) => !existingKeys.has(keyOf(d))) .map((d) => ({ + workspaceId, referencePageId, sourcePageId: d.sourcePageId, transclusionId: d.transclusionId, @@ -156,7 +162,7 @@ export class TransclusionService { * (e.g. duplication, import) where there is nothing to diff against. */ async insertTransclusionsForPages( - pages: Array<{ id: string; content: unknown }>, + pages: Array<{ id: string; workspaceId: string; content: unknown }>, trx?: KyselyTransaction, ): Promise<{ inserted: number }> { const rows: Parameters[0] = []; @@ -164,6 +170,7 @@ export class TransclusionService { const snapshots = collectTransclusionsFromPmJson(page.content); for (const s of snapshots) { rows.push({ + workspaceId: page.workspaceId, pageId: page.id, transclusionId: s.transclusionId, content: s.content as any, @@ -181,10 +188,11 @@ export class TransclusionService { * (duplication, import) where there is nothing to diff against. */ async insertReferencesForPages( - pages: Array<{ id: string; content: unknown }>, + pages: Array<{ id: string; workspaceId: string; content: unknown }>, trx?: KyselyTransaction, ): Promise<{ inserted: number }> { const rows: Array<{ + workspaceId: string; referencePageId: string; sourcePageId: string; transclusionId: string; @@ -193,6 +201,7 @@ export class TransclusionService { const refs = collectReferencesFromPmJson(page.content); for (const r of refs) { rows.push({ + workspaceId: page.workspaceId, referencePageId: page.id, sourcePageId: r.sourcePageId, transclusionId: r.transclusionId, @@ -206,23 +215,22 @@ export class TransclusionService { async lookup( references: Array<{ sourcePageId: string; transclusionId: string }>, - viewerUserId: string | null, + viewerUserId: string, + workspaceId: string, ): Promise<{ items: TransclusionLookup[] }> { if (references.length === 0) return { items: [] }; const candidatePageIds = Array.from( new Set(references.map((r) => r.sourcePageId)), ); - const accessibleSet = viewerUserId - ? new Set( - await this.pagePermissionRepo.filterAccessiblePageIds({ - pageIds: candidatePageIds, - userId: viewerUserId, - }), - ) - : new Set(); + const accessibleSet = new Set( + await this.pagePermissionRepo.filterAccessiblePageIds({ + pageIds: candidatePageIds, + userId: viewerUserId, + }), + ); - return this.lookupWithAccessSet(references, accessibleSet); + return this.lookupWithAccessSet(references, accessibleSet, workspaceId); } /** @@ -234,6 +242,7 @@ export class TransclusionService { async lookupWithAccessSet( references: Array<{ sourcePageId: string; transclusionId: string }>, accessibleSet: Set, + workspaceId: string, ): Promise<{ items: TransclusionLookup[] }> { if (references.length === 0) return { items: [] }; @@ -248,6 +257,7 @@ export class TransclusionService { pageId: references[i].sourcePageId, transclusionId: references[i].transclusionId, })), + workspaceId, ); const rowKey = (r: { pageId: string; transclusionId: string }) => `${r.pageId}::${r.transclusionId}`; @@ -256,10 +266,12 @@ export class TransclusionService { const accessiblePageIds = Array.from( new Set(accessiblePending.map((i) => references[i].sourcePageId)), ); - const pages = await this.pageRepo.findManyByIds(accessiblePageIds); + const pages = await this.pageRepo.findManyByIds(accessiblePageIds, { + workspaceId, + }); const pageMeta = new Map(); for (const p of pages) { - if (!p.deletedAt) pageMeta.set(p.id, p.updatedAt); + pageMeta.set(p.id, p.updatedAt); } for (const i of pendingIdx) { @@ -306,16 +318,18 @@ export class TransclusionService { sourcePageId: string; transclusionId: string; viewerUserId: string; + workspaceId: string; }): Promise<{ source: ReferencingPageInfo | null; references: ReferencingPageInfo[]; }> { - const { sourcePageId, transclusionId, viewerUserId } = opts; + const { sourcePageId, transclusionId, viewerUserId, workspaceId } = opts; const referencePageIds = await this.pageTransclusionReferencesRepo.findReferencePageIdsByTransclusion( sourcePageId, transclusionId, + workspaceId, ); const candidatePageIds = Array.from( @@ -342,7 +356,7 @@ export class TransclusionService { ); const byId = new Map(); for (const p of rows) { - if (!p || p.deletedAt) continue; + if (!p || p.deletedAt || p.workspaceId !== workspaceId) continue; const space = (p as Page & { space?: { slug?: string } }).space; byId.set(p.id, { id: p.id, @@ -376,7 +390,7 @@ export class TransclusionService { referencePageId: string, sourcePageId: string, transclusionId: string, - viewerUserId: string, + user: User, ): Promise<{ content: unknown }> { const referencePage = await this.pageRepo.findById(referencePageId); if (!referencePage || referencePage.deletedAt) { @@ -388,16 +402,16 @@ export class TransclusionService { throw new NotFoundException('Source page not found'); } - const accessible = new Set( - await this.pagePermissionRepo.filterAccessiblePageIds({ - pageIds: [referencePageId, sourcePageId], - userId: viewerUserId, - }), - ); - if (!accessible.has(referencePageId) || !accessible.has(sourcePageId)) { + if ( + referencePage.workspaceId !== user.workspaceId || + sourcePage.workspaceId !== user.workspaceId + ) { throw new ForbiddenException(); } + await this.pageAccessService.validateCanEdit(referencePage, user); + await this.pageAccessService.validateCanView(sourcePage, user); + const transclusion = await this.pageTransclusionsRepo.findByPageAndTransclusion( sourcePageId, @@ -445,7 +459,7 @@ export class TransclusionService { fileSize: old.fileSize, mimeType: old.mimeType, fileExt: old.fileExt, - creatorId: viewerUserId, + creatorId: user.id, workspaceId: referencePage.workspaceId, pageId: referencePageId, spaceId: referencePage.spaceId, diff --git a/apps/server/src/core/share/share.service.ts b/apps/server/src/core/share/share.service.ts index cfa11db2..03ee3155 100644 --- a/apps/server/src/core/share/share.service.ts +++ b/apps/server/src/core/share/share.service.ts @@ -357,6 +357,7 @@ export class ShareService { const { items } = await this.transclusionService.lookupWithAccessSet( references, accessibleSet, + workspaceId, ); // Sanitize each item's content for public delivery diff --git a/apps/server/src/database/migrations/20260501T202258-page-transclusions.ts b/apps/server/src/database/migrations/20260501T202258-page-transclusions.ts index a4f502e6..2316536b 100644 --- a/apps/server/src/database/migrations/20260501T202258-page-transclusions.ts +++ b/apps/server/src/database/migrations/20260501T202258-page-transclusions.ts @@ -6,6 +6,9 @@ export async function up(db: Kysely): Promise { .addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_uuid_v7()`), ) + .addColumn('workspace_id', 'uuid', (col) => + col.notNull().references('workspaces.id').onDelete('cascade'), + ) .addColumn('page_id', 'uuid', (col) => col.notNull().references('pages.id').onDelete('cascade'), ) @@ -23,11 +26,20 @@ export async function up(db: Kysely): Promise { ]) .execute(); + await db.schema + .createIndex('idx_page_transclusions_workspace') + .on('page_transclusions') + .column('workspace_id') + .execute(); + await db.schema .createTable('page_transclusion_references') .addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_uuid_v7()`), ) + .addColumn('workspace_id', 'uuid', (col) => + col.notNull().references('workspaces.id').onDelete('cascade'), + ) .addColumn('reference_page_id', 'uuid', (col) => col.notNull().references('pages.id').onDelete('cascade'), ) @@ -50,6 +62,12 @@ export async function up(db: Kysely): Promise { .on('page_transclusion_references') .columns(['source_page_id', 'transclusion_id']) .execute(); + + await db.schema + .createIndex('idx_page_transclusion_references_workspace') + .on('page_transclusion_references') + .column('workspace_id') + .execute(); } export async function down(db: Kysely): Promise { diff --git a/apps/server/src/database/repos/page-transclusions/page-transclusion-references.repo.ts b/apps/server/src/database/repos/page-transclusions/page-transclusion-references.repo.ts index f195dc50..f149fb36 100644 --- a/apps/server/src/database/repos/page-transclusions/page-transclusion-references.repo.ts +++ b/apps/server/src/database/repos/page-transclusions/page-transclusion-references.repo.ts @@ -30,12 +30,14 @@ export class PageTransclusionReferencesRepo { async findReferencePageIdsByTransclusion( sourcePageId: string, transclusionId: string, + workspaceId: string, trx?: KyselyTransaction, ): Promise { const rows = await dbOrTx(this.db, trx) .selectFrom('pageTransclusionReferences') .select('referencePageId') .distinct() + .where('workspaceId', '=', workspaceId) .where('sourcePageId', '=', sourcePageId) .where('transclusionId', '=', transclusionId) .execute(); diff --git a/apps/server/src/database/repos/page-transclusions/page-transclusions.repo.ts b/apps/server/src/database/repos/page-transclusions/page-transclusions.repo.ts index 8df1ce08..ac3e6176 100644 --- a/apps/server/src/database/repos/page-transclusions/page-transclusions.repo.ts +++ b/apps/server/src/database/repos/page-transclusions/page-transclusions.repo.ts @@ -39,12 +39,14 @@ export class PageTransclusionsRepo { async findManyByPageAndTransclusion( keys: Array<{ pageId: string; transclusionId: string }>, + workspaceId: string, trx?: KyselyTransaction, ): Promise { if (keys.length === 0) return []; return dbOrTx(this.db, trx) .selectFrom('pageTransclusions') .selectAll() + .where('workspaceId', '=', workspaceId) .where((eb) => eb.or( keys.map((k) => diff --git a/apps/server/src/database/repos/page/page.repo.ts b/apps/server/src/database/repos/page/page.repo.ts index 6148641c..6b17e37f 100644 --- a/apps/server/src/database/repos/page/page.repo.ts +++ b/apps/server/src/database/repos/page/page.repo.ts @@ -104,16 +104,24 @@ export class PageRepo { pageIds: string[], opts?: { trx?: KyselyTransaction; + workspaceId?: string; }, ): Promise { if (pageIds.length === 0) return []; const db = dbOrTx(this.db, opts?.trx); - return db + let query = db .selectFrom('pages') .select(this.baseFields) - .where('id', 'in', pageIds) - .execute(); + .where('id', 'in', pageIds); + + if (opts?.workspaceId) { + query = query + .where('workspaceId', '=', opts.workspaceId) + .where('deletedAt', 'is', null); + } + + return query.execute(); } async updatePage( diff --git a/apps/server/src/database/types/db.d.ts b/apps/server/src/database/types/db.d.ts index 9cc18dba..e23df60d 100644 --- a/apps/server/src/database/types/db.d.ts +++ b/apps/server/src/database/types/db.d.ts @@ -234,6 +234,7 @@ export interface PageTransclusionReferences { referencePageId: string; id: Generated; sourcePageId: string; + workspaceId: string; } export interface PageTransclusions { @@ -243,6 +244,7 @@ export interface PageTransclusions { id: Generated; pageId: string; updatedAt: Generated; + workspaceId: string; } export interface PageHistory { diff --git a/apps/server/src/ee b/apps/server/src/ee index 35c0f3c4..64795229 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit 35c0f3c4f8c37595f5e1bcc446dc77a4e1cb021b +Subproject commit 6479522986f62b30f06f3e65eeec83b713114941