fix(git-sync): don't trash a page on cross-space move (move-to-space data loss)

A page moved to another space with git-sync enabled was sent to Trash and
vanished from BOTH vaults. The source space's push phase sees the moved-away
page's file gone from its vault and calls deletePage -> soft-delete, even though
the page still lives in the destination space.

Thread the reconciling spaceId into the bind context and, in deletePage, skip the
soft-delete when the page's CURRENT space differs from the space being reconciled
(a move-out): only the vault file is dropped, the page is preserved. Genuine
in-space deletions are unaffected (space matches).

Found by autonomous QA (web-test-orchestrator). Control: with git-sync OFF the
move keeps deleted_at NULL; with it ON the page was trashed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
claude-stand
2026-07-02 13:47:48 +03:00
parent 123e981808
commit c7e034cab9
2 changed files with 31 additions and 1 deletions
@@ -23,6 +23,14 @@ import { AuthProvenanceData } from '../../../common/decorators/auth-provenance.d
export interface GitSyncBindContext {
workspaceId: string;
userId: string;
/**
* The space this cycle reconciles. Used to distinguish a genuine page deletion
* from a cross-space MOVE: when a page leaves space A, A's vault file is removed
* and the push phase would otherwise soft-delete the page — but the page still
* lives in space B. `deletePage` skips the soft-delete when the page's current
* space differs from the reconciling space. Optional for back-compat.
*/
spaceId?: string;
}
/**
@@ -237,6 +245,24 @@ export class GitmostDataSourceService {
ctx: GitSyncBindContext,
pageId: string,
): Promise<unknown> {
// Cross-space MOVE guard. A push-phase delete fires when a page's file
// disappears from THIS space's vault. That happens for a genuine deletion —
// but ALSO when the page was moved to another space (source vault file
// removed, page recreated in the destination vault). In the move case the
// page still exists and must NOT be trashed: soft-deleting it here loses the
// page from BOTH vaults and dumps it in Trash (observed data-loss on
// move-to-space with git-sync enabled). If the page's CURRENT space differs
// from the space we're reconciling, this is a move-out — drop only the vault
// file (already done by the engine), never the page.
if (ctx.spaceId) {
const page = await this.pageRepo.findById(pageId);
if (page && page.deletedAt == null && page.spaceId !== ctx.spaceId) {
this.logger.log(
`git-sync[${ctx.spaceId}] skip delete of page ${pageId}: moved to space ${page.spaceId} (vault file removed, page preserved)`,
);
return { id: pageId, skipped: 'moved-to-other-space' };
}
}
await this.pageService.removePage(
pageId,
ctx.userId,