fix(member-work-sync): lock pending report writes

This commit is contained in:
777genius 2026-04-29 14:16:10 +03:00
parent cd70fc09cc
commit bf1f3b6b02
2 changed files with 63 additions and 52 deletions

View file

@ -2,6 +2,7 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const crypto = require('crypto'); const crypto = require('crypto');
const runtimeHelpers = require('./runtimeHelpers.js'); const runtimeHelpers = require('./runtimeHelpers.js');
const { withFileLockSync } = require('./fileLock.js');
const DEFAULT_WAIT_TIMEOUT_MS = 10000; const DEFAULT_WAIT_TIMEOUT_MS = 10000;
const MIN_WAIT_TIMEOUT_MS = 1000; const MIN_WAIT_TIMEOUT_MS = 1000;
@ -173,16 +174,17 @@ function writePendingReportFile(filePath, data) {
function appendPendingReportIntent(context, body, reason) { function appendPendingReportIntent(context, body, reason) {
const filePath = path.join(context.paths.teamDir, '.member-work-sync', 'pending-reports.json'); const filePath = path.join(context.paths.teamDir, '.member-work-sync', 'pending-reports.json');
const { id } = withFileLockSync(filePath, () => {
const data = readPendingReportFile(filePath); const data = readPendingReportFile(filePath);
const request = { const request = {
...body, ...body,
source: 'mcp', source: 'mcp',
}; };
const id = buildPendingIntentId(request); const intentId = buildPendingIntentId(request);
const current = data.intents[id]; const current = data.intents[intentId];
if (!current || current.status === 'pending') { if (!current || current.status === 'pending') {
data.intents[id] = { data.intents[intentId] = {
id, id: intentId,
teamName: body.teamName, teamName: body.teamName,
memberName: body.memberName, memberName: body.memberName,
request, request,
@ -192,6 +194,8 @@ function appendPendingReportIntent(context, body, reason) {
}; };
writePendingReportFile(filePath, data); writePendingReportFile(filePath, data);
} }
return { id: intentId };
});
return { return {
accepted: false, accepted: false,
pendingValidation: true, pendingValidation: true,

View file

@ -2,6 +2,7 @@ import { atomicWriteAsync } from '@main/utils/atomicWrite';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { mkdir, readFile, rename } from 'fs/promises'; import { mkdir, readFile, rename } from 'fs/promises';
import { withFileLock } from '@main/services/team/fileLock';
import type { import type {
MemberWorkSyncReportIntent, MemberWorkSyncReportIntent,
MemberWorkSyncReportRequest, MemberWorkSyncReportRequest,
@ -106,6 +107,7 @@ export class JsonMemberWorkSyncStore
async write(status: MemberWorkSyncStatus): Promise<void> { async write(status: MemberWorkSyncStatus): Promise<void> {
await this.enqueue(status.teamName, async () => { await this.enqueue(status.teamName, async () => {
await withFileLock(this.paths.getStatusPath(status.teamName), async () => {
const existing = await this.readFile(status.teamName); const existing = await this.readFile(status.teamName);
existing.members[normalizeMemberKey(status.memberName)] = status; existing.members[normalizeMemberKey(status.memberName)] = status;
await mkdir(this.paths.getTeamDir(status.teamName), { recursive: true }); await mkdir(this.paths.getTeamDir(status.teamName), { recursive: true });
@ -114,11 +116,13 @@ export class JsonMemberWorkSyncStore
JSON.stringify(existing, null, 2) JSON.stringify(existing, null, 2)
); );
}); });
});
} }
async appendPendingReport(request: MemberWorkSyncReportRequest, reason: string): Promise<void> { async appendPendingReport(request: MemberWorkSyncReportRequest, reason: string): Promise<void> {
const id = buildPendingReportIntentId(request); const id = buildPendingReportIntentId(request);
await this.enqueue(request.teamName, async () => { await this.enqueue(request.teamName, async () => {
await withFileLock(this.paths.getPendingReportsPath(request.teamName), async () => {
const existing = await this.readPendingFile(request.teamName); const existing = await this.readPendingFile(request.teamName);
const current = existing.intents[id]; const current = existing.intents[id];
if (current && current.status !== 'pending') { if (current && current.status !== 'pending') {
@ -135,6 +139,7 @@ export class JsonMemberWorkSyncStore
}; };
await this.writePendingFile(request.teamName, existing); await this.writePendingFile(request.teamName, existing);
}); });
});
} }
async listPendingReports(teamName: string): Promise<MemberWorkSyncReportIntent[]> { async listPendingReports(teamName: string): Promise<MemberWorkSyncReportIntent[]> {
@ -154,6 +159,7 @@ export class JsonMemberWorkSyncStore
} }
): Promise<void> { ): Promise<void> {
await this.enqueue(teamName, async () => { await this.enqueue(teamName, async () => {
await withFileLock(this.paths.getPendingReportsPath(teamName), async () => {
const existing = await this.readPendingFile(teamName); const existing = await this.readPendingFile(teamName);
const current = existing.intents[id]; const current = existing.intents[id];
if (!current || current.status !== 'pending') { if (!current || current.status !== 'pending') {
@ -167,6 +173,7 @@ export class JsonMemberWorkSyncStore
}; };
await this.writePendingFile(teamName, existing); await this.writePendingFile(teamName, existing);
}); });
});
} }
private async readFile(teamName: string): Promise<StoreFile> { private async readFile(teamName: string): Promise<StoreFile> {