import { JsonMemberWorkSyncStore } from '@features/member-work-sync/main/infrastructure/JsonMemberWorkSyncStore';
import { MemberWorkSyncStorePaths } from '@features/member-work-sync/main/infrastructure/MemberWorkSyncStorePaths';
import { mkdir, mkdtemp, readdir, readFile, rm, writeFile } from 'fs/promises';
import { tmpdir } from 'os';
import { join } from 'path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';

import type {
  MemberWorkSyncNudgePayload,
  MemberWorkSyncStatus,
} from '@features/member-work-sync/contracts';
import type { MemberWorkSyncAuditEvent } from '@features/member-work-sync/core/application';

function makeStatus(overrides: Partial<MemberWorkSyncStatus>): MemberWorkSyncStatus {
  return {
    teamName: 'team-a',
    memberName: 'bob',
    state: 'needs_sync',
    agenda: {
      teamName: 'team-a',
      memberName: 'bob',
      generatedAt: '2026-04-29T00:00:00.000Z',
      fingerprint: 'agenda:v1:abc',
      items: [
        {
          taskId: 'task-1',
          displayId: '11111111',
          subject: 'Ship UI',
          kind: 'work',
          assignee: 'bob',
          priority: 'normal',
          reason: 'owned_pending_task',
          evidence: { status: 'pending', owner: 'bob' },
        },
      ],
      diagnostics: [],
    },
    shadow: {
      reconciledBy: 'queue',
      wouldNudge: true,
      fingerprintChanged: false,
    },
    evaluatedAt: '2026-04-29T00:00:00.000Z',
    diagnostics: [],
    ...overrides,
  };
}

function makeNudgePayload(
  overrides: Partial<MemberWorkSyncNudgePayload> = {}
): MemberWorkSyncNudgePayload {
  return {
    from: 'system',
    to: 'bob',
    messageKind: 'member_work_sync_nudge',
    source: 'member-work-sync',
    actionMode: 'do',
    workSyncIntent: 'agenda_sync',
    text: 'Work sync check: continue the current task or report a blocker.',
    taskRefs: [{ teamName: 'team-a', taskId: 'task-1', displayId: '11111111' }],
    ...overrides,
  };
}

function memberWorkSyncDir(root: string, teamName: string, memberName: string): string {
  return join(
    root,
    teamName,
    'members',
    encodeURIComponent(memberName.trim().toLowerCase()),
    '.member-work-sync'
  );
}

describe('JsonMemberWorkSyncStore', () => {
  let root: string;
  let store: JsonMemberWorkSyncStore;

  beforeEach(async () => {
    root = await mkdtemp(join(tmpdir(), 'member-work-sync-store-'));
    store = new JsonMemberWorkSyncStore(new MemberWorkSyncStorePaths(root));
  });

  afterEach(async () => {
    await rm(root, { recursive: true, force: true });
  });

  it('quarantines invalid status JSON and returns empty state', async () => {
    const statusPath = join(root, 'team-a', '.member-work-sync', 'status.json');
    await mkdir(join(root, 'team-a', '.member-work-sync'), { recursive: true });
    await writeFile(statusPath, '{bad json', 'utf8');

    await expect(store.read({ teamName: 'team-a', memberName: 'bob' })).resolves.toBeNull();

    const teamDir = join(root, 'team-a', '.member-work-sync');
    const entries = await readdir(teamDir);
    expect(entries.some((entry) => entry.startsWith('status.json.invalid.'))).toBe(true);
  });

  it('writes status into member-scoped storage and keeps team metrics in an index', async () => {
    await store.write(makeStatus({ providerId: 'opencode' }));

    const statusFile = JSON.parse(
      await readFile(join(memberWorkSyncDir(root, 'team-a', 'bob'), 'status.json'), 'utf8')
    );
    expect(statusFile).toMatchObject({
      schemaVersion: 2,
      status: {
        teamName: 'team-a',
        memberName: 'bob',
        providerId: 'opencode',
      },
    });

    const metaFile = JSON.parse(
      await readFile(join(root, 'team-a', 'members', 'bob', 'member.meta.json'), 'utf8')
    );
    expect(metaFile).toMatchObject({
      schemaVersion: 1,
      memberName: 'bob',
      memberKey: 'bob',
    });

    const metricsIndex = JSON.parse(
      await readFile(join(root, 'team-a', '.member-work-sync', 'indexes', 'metrics.json'), 'utf8')
    );
    expect(metricsIndex.members.bob).toMatchObject({
      memberName: 'bob',
      state: 'needs_sync',
      actionableCount: 1,
    });
  });

  it('prefers member-scoped v2 status over legacy v1 status', async () => {
    await store.write(
      makeStatus({ state: 'caught_up', agenda: { ...makeStatus({}).agenda, items: [] } })
    );

    const legacyStatusPath = join(root, 'team-a', '.member-work-sync', 'status.json');
    await mkdir(join(root, 'team-a', '.member-work-sync'), { recursive: true });
    await writeFile(
      legacyStatusPath,
      JSON.stringify({ schemaVersion: 1, members: { bob: makeStatus({ state: 'needs_sync' }) } }),
      'utf8'
    );

    await expect(store.read({ teamName: 'team-a', memberName: 'bob' })).resolves.toMatchObject({
      state: 'caught_up',
    });
  });

  it('deduplicates pending report intents and marks them processed', async () => {
    const request = {
      teamName: 'team-a',
      memberName: 'bob',
      state: 'still_working' as const,
      agendaFingerprint: 'agenda:v1:abc',
      reportToken: 'wrs:v1.test',
      taskIds: ['task-2', 'task-1', 'task-1'],
      source: 'mcp' as const,
    };

    await store.appendPendingReport(request, 'control_api_unavailable');
    await store.appendPendingReport({ ...request, taskIds: ['task-1', 'task-2'] }, 'duplicate');

    const pending = await store.listPendingReports('team-a');
    expect(pending).toHaveLength(1);
    expect(pending[0]).toMatchObject({
      teamName: 'team-a',
      memberName: 'bob',
      reason: 'control_api_unavailable',
      status: 'pending',
    });

    await store.markPendingReportProcessed('team-a', pending[0].id, {
      status: 'accepted',
      resultCode: 'accepted',
      processedAt: '2026-04-29T00:00:00.000Z',
    });

    expect(await store.listPendingReports('team-a')).toEqual([]);
    const file = JSON.parse(
      await readFile(join(memberWorkSyncDir(root, 'team-a', 'bob'), 'reports.json'), 'utf8')
    );
    expect(file.intents[pending[0].id]).toMatchObject({
      status: 'accepted',
      resultCode: 'accepted',
    });
    const index = JSON.parse(
      await readFile(
        join(root, 'team-a', '.member-work-sync', 'indexes', 'pending-reports-index.json'),
        'utf8'
      )
    );
    expect(index.items[pending[0].id]).toMatchObject({
      memberName: 'bob',
      status: 'accepted',
    });
  });

  it('repairs a missing pending-report index from member-scoped report files', async () => {
    const request = {
      teamName: 'team-a',
      memberName: 'bob',
      state: 'still_working' as const,
      agendaFingerprint: 'agenda:v1:abc',
      reportToken: 'wrs:v1.test',
      source: 'mcp' as const,
    };

    await store.appendPendingReport(request, 'control_api_unavailable');
    await rm(join(root, 'team-a', '.member-work-sync', 'indexes', 'pending-reports-index.json'), {
      force: true,
    });

    await expect(store.listPendingReports('team-a')).resolves.toHaveLength(1);
    const repaired = JSON.parse(
      await readFile(
        join(root, 'team-a', '.member-work-sync', 'indexes', 'pending-reports-index.json'),
        'utf8'
      )
    );
    expect(Object.values(repaired.items)).toEqual([
      expect.objectContaining({ memberName: 'bob', status: 'pending' }),
    ]);
  });

  it('repairs a stale pending-report index route from member-scoped report files', async () => {
    const bobRequest = {
      teamName: 'team-a',
      memberName: 'bob',
      state: 'still_working' as const,
      agendaFingerprint: 'agenda:v1:bob',
      reportToken: 'wrs:v1.bob',
      source: 'mcp' as const,
    };
    const tomRequest = {
      ...bobRequest,
      memberName: 'tom',
      agendaFingerprint: 'agenda:v1:tom',
      reportToken: 'wrs:v1.tom',
    };

    await store.appendPendingReport(bobRequest, 'control_api_unavailable');
    await store.appendPendingReport(tomRequest, 'control_api_unavailable');
    await writeFile(
      join(root, 'team-a', 'members', 'bob', '.member-work-sync', 'reports.json'),
      JSON.stringify({ schemaVersion: 2, intents: {} }),
      'utf8'
    );

    const pending = await store.listPendingReports('team-a');
    expect(pending.map((intent) => intent.memberName)).toEqual(['tom']);
    const repaired = JSON.parse(
      await readFile(
        join(root, 'team-a', '.member-work-sync', 'indexes', 'pending-reports-index.json'),
        'utf8'
      )
    );
    expect(
      Object.values(repaired.items).map((item) => (item as { memberName: string }).memberName)
    ).toEqual(['tom']);
  });

  it('repairs a partially missing pending-report index route from member-scoped report files', async () => {
    const bobRequest = {
      teamName: 'team-a',
      memberName: 'bob',
      state: 'still_working' as const,
      agendaFingerprint: 'agenda:v1:bob',
      reportToken: 'wrs:v1.bob',
      source: 'mcp' as const,
    };
    const tomRequest = {
      ...bobRequest,
      memberName: 'tom',
      agendaFingerprint: 'agenda:v1:tom',
      reportToken: 'wrs:v1.tom',
    };

    await store.appendPendingReport(bobRequest, 'control_api_unavailable');
    await store.appendPendingReport(tomRequest, 'control_api_unavailable');
    const indexPath = join(
      root,
      'team-a',
      '.member-work-sync',
      'indexes',
      'pending-reports-index.json'
    );
    const index = JSON.parse(await readFile(indexPath, 'utf8'));
    for (const [id, route] of Object.entries(index.items)) {
      if ((route as { memberName: string }).memberName === 'tom') {
        delete index.items[id];
      }
    }
    await writeFile(indexPath, JSON.stringify(index), 'utf8');

    const pending = await store.listPendingReports('team-a');
    expect(pending.map((intent) => intent.memberName).sort()).toEqual(['bob', 'tom']);
    const repaired = JSON.parse(await readFile(indexPath, 'utf8'));
    expect(
      Object.values(repaired.items)
        .map((item) => (item as { memberName: string }).memberName)
        .sort()
    ).toEqual(['bob', 'tom']);
  });

  it('repairs a stale processed pending-report index route when member report is pending', async () => {
    const request = {
      teamName: 'team-a',
      memberName: 'bob',
      state: 'still_working' as const,
      agendaFingerprint: 'agenda:v1:abc',
      reportToken: 'wrs:v1.test',
      source: 'mcp' as const,
    };

    await store.appendPendingReport(request, 'control_api_unavailable');
    const [intent] = await store.listPendingReports('team-a');
    await store.markPendingReportProcessed('team-a', intent!.id, {
      status: 'accepted',
      resultCode: 'accepted',
      processedAt: '2026-04-29T00:01:00.000Z',
    });

    const reportsPath = join(memberWorkSyncDir(root, 'team-a', 'bob'), 'reports.json');
    const reports = JSON.parse(await readFile(reportsPath, 'utf8'));
    reports.intents[intent!.id] = {
      ...reports.intents[intent!.id],
      status: 'pending',
    };
    delete reports.intents[intent!.id].resultCode;
    delete reports.intents[intent!.id].processedAt;
    await writeFile(reportsPath, JSON.stringify(reports), 'utf8');

    const pending = await store.listPendingReports('team-a');
    expect(pending).toHaveLength(1);
    expect(pending[0]).toMatchObject({
      id: intent!.id,
      memberName: 'bob',
      status: 'pending',
    });
  });

  it('repairs stale pending-report update routes before marking processed', async () => {
    const request = {
      teamName: 'team-a',
      memberName: 'bob',
      state: 'still_working' as const,
      agendaFingerprint: 'agenda:v1:abc',
      reportToken: 'wrs:v1.test',
      source: 'mcp' as const,
    };

    await store.appendPendingReport(request, 'control_api_unavailable');
    await mkdir(memberWorkSyncDir(root, 'team-a', 'tom'), { recursive: true });
    const [intent] = await store.listPendingReports('team-a');
    await writeFile(
      join(memberWorkSyncDir(root, 'team-a', 'tom'), 'reports.json'),
      JSON.stringify({
        schemaVersion: 2,
        intents: {
          [intent!.id]: {
            ...intent!,
            teamName: 'other-team',
            memberName: 'tom',
          },
        },
      }),
      'utf8'
    );
    const indexPath = join(
      root,
      'team-a',
      '.member-work-sync',
      'indexes',
      'pending-reports-index.json'
    );
    const index = JSON.parse(await readFile(indexPath, 'utf8'));
    index.items[intent!.id] = {
      ...index.items[intent!.id],
      memberKey: 'tom',
      memberName: 'tom',
    };
    await writeFile(indexPath, JSON.stringify(index), 'utf8');

    await store.markPendingReportProcessed('team-a', intent!.id, {
      status: 'accepted',
      resultCode: 'accepted',
      processedAt: '2026-04-29T00:01:00.000Z',
    });

    const reports = JSON.parse(
      await readFile(join(memberWorkSyncDir(root, 'team-a', 'bob'), 'reports.json'), 'utf8')
    );
    expect(reports.intents[intent!.id]).toMatchObject({
      memberName: 'bob',
      status: 'accepted',
      resultCode: 'accepted',
    });
    const repaired = JSON.parse(await readFile(indexPath, 'utf8'));
    expect(repaired.items[intent!.id]).toMatchObject({
      memberKey: 'bob',
      memberName: 'bob',
      status: 'accepted',
    });
  });

  it('records bounded shadow metrics from status writes', async () => {
    await store.write(makeStatus({}));
    await store.write(
      makeStatus({
        agenda: {
          teamName: 'team-a',
          memberName: 'bob',
          generatedAt: '2026-04-29T00:01:00.000Z',
          fingerprint: 'agenda:v1:def',
          items: [],
          diagnostics: [],
        },
        state: 'caught_up',
        shadow: {
          reconciledBy: 'request',
          wouldNudge: false,
          fingerprintChanged: true,
          previousFingerprint: 'agenda:v1:abc',
        },
        evaluatedAt: '2026-04-29T00:01:00.000Z',
      })
    );

    const metrics = await store.readTeamMetrics('team-a');
    expect(metrics).toMatchObject({
      teamName: 'team-a',
      memberCount: 1,
      actionableItemCount: 0,
      wouldNudgeCount: 1,
      fingerprintChangeCount: 1,
    });
    expect(metrics.stateCounts.caught_up).toBe(1);
    expect(metrics.recentEvents.map((event) => event.kind)).toEqual([
      'status_evaluated',
      'would_nudge',
      'status_evaluated',
      'fingerprint_changed',
    ]);
    expect(metrics.phase2Readiness).toMatchObject({
      state: 'collecting_shadow_data',
      reasons: expect.arrayContaining([
        'insufficient_status_events',
        'insufficient_observation_window',
      ]),
    });
  });

  it('refreshes undelivered outbox payloads but rejects delivered payload conflicts', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };

    await expect(store.ensurePending(input)).resolves.toMatchObject({
      ok: true,
      outcome: 'created',
      item: { status: 'pending', attemptGeneration: 0 },
    });
    await expect(store.ensurePending(input)).resolves.toMatchObject({
      ok: true,
      outcome: 'existing',
    });
    const refreshed = await store.ensurePending({
      ...input,
      payloadHash: 'hash-b',
      payload: makeNudgePayload({
        text: 'Work sync check: call member_work_sync_status and member_work_sync_report.',
      }),
      nowIso: '2026-04-29T00:01:00.000Z',
    });
    expect(refreshed).toMatchObject({
      ok: true,
      outcome: 'existing',
      item: {
        status: 'pending',
        payloadHash: 'hash-b',
        payload: {
          text: 'Work sync check: call member_work_sync_status and member_work_sync_report.',
        },
      },
    });

    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:02:00.000Z',
      limit: 1,
    });
    const claimedRefresh = await store.ensurePending({
      ...input,
      payloadHash: 'hash-c',
      payload: makeNudgePayload({ text: 'New text while delivery is claimed.' }),
      nowIso: '2026-04-29T00:02:30.000Z',
    });
    expect(claimedRefresh).toMatchObject({
      ok: true,
      outcome: 'existing',
      item: {
        status: 'pending',
        payloadHash: 'hash-c',
        payload: { text: 'New text while delivery is claimed.' },
        attemptGeneration: claimed.attemptGeneration + 1,
      },
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      deliveredMessageId: 'message-1',
      nowIso: '2026-04-29T00:03:00.000Z',
    });
    const afterStaleDelivery = JSON.parse(
      await readFile(
        join(root, 'team-a', 'members', 'bob', '.member-work-sync', 'outbox.json'),
        'utf8'
      )
    );
    expect(afterStaleDelivery.items[input.id]).toMatchObject({
      status: 'pending',
      payloadHash: 'hash-c',
    });

    const [reclaimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-b',
      nowIso: '2026-04-29T00:03:30.000Z',
      limit: 1,
    });
    expect(reclaimed).toMatchObject({
      id: input.id,
      payloadHash: 'hash-c',
      attemptGeneration: claimed.attemptGeneration + 2,
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: reclaimed.attemptGeneration,
      deliveredMessageId: 'message-2',
      nowIso: '2026-04-29T00:03:45.000Z',
    });

    await expect(
      store.ensurePending({
        ...input,
        payloadHash: 'hash-d',
        payload: makeNudgePayload({ text: 'New text after delivery.' }),
        nowIso: '2026-04-29T00:04:00.000Z',
      })
    ).resolves.toMatchObject({
      ok: false,
      outcome: 'payload_conflict',
      existingPayloadHash: 'hash-c',
      requestedPayloadHash: 'hash-d',
    });
  });

  it('revives superseded outbox items but keeps delivered nudges one-per-fingerprint', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };

    await store.ensurePending(input);
    await store.markSuperseded({
      teamName: 'team-a',
      id: input.id,
      reason: 'status_no_longer_matches_outbox',
      nowIso: '2026-04-29T00:01:00.000Z',
    });

    const revived = await store.ensurePending({ ...input, nowIso: '2026-04-29T00:02:00.000Z' });
    expect(revived).toMatchObject({
      ok: true,
      outcome: 'existing',
      item: { status: 'pending' },
    });
    expect(revived.item).not.toHaveProperty('lastError');

    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:03:00.000Z',
      limit: 1,
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      deliveredMessageId: 'message-1',
      nowIso: '2026-04-29T00:04:00.000Z',
    });

    await expect(
      store.ensurePending({ ...input, nowIso: '2026-04-29T00:05:00.000Z' })
    ).resolves.toMatchObject({
      ok: true,
      outcome: 'existing',
      item: { status: 'delivered', deliveredMessageId: 'message-1' },
    });
  });

  it('clears stale retry delay when a fresh reconcile revives the same outbox item', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };

    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markFailed({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      retryable: true,
      error: 'member_busy:active_tool_activity',
      nextAttemptAt: '2026-04-29T00:30:00.000Z',
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    const revived = await store.ensurePending({ ...input, nowIso: '2026-04-29T00:03:00.000Z' });

    expect(revived).toMatchObject({
      ok: true,
      outcome: 'existing',
      item: { status: 'pending', attemptGeneration: 1 },
    });
    expect(revived.item).not.toHaveProperty('nextAttemptAt');
    expect(revived.item).not.toHaveProperty('lastError');

    const [reclaimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-b',
      nowIso: '2026-04-29T00:04:00.000Z',
      limit: 1,
    });
    expect(reclaimed).toMatchObject({ id: input.id, attemptGeneration: 2 });
  });

  it('keeps an explicitly requested retry delay when reviving an outbox item', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };

    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markFailed({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      retryable: true,
      error: 'member_busy:active_tool_activity',
      nextAttemptAt: '2026-04-29T00:30:00.000Z',
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    const revived = await store.ensurePending({
      ...input,
      nextAttemptAt: '2026-04-29T00:10:00.000Z',
      nowIso: '2026-04-29T00:03:00.000Z',
    });

    expect(revived.item).toMatchObject({
      status: 'pending',
      nextAttemptAt: '2026-04-29T00:10:00.000Z',
    });
    await expect(
      store.claimDue({
        teamName: 'team-a',
        claimedBy: 'dispatcher-b',
        nowIso: '2026-04-29T00:04:00.000Z',
        limit: 1,
      })
    ).resolves.toEqual([]);
  });

  it('treats invalid retry delay timestamps as due so retryable items cannot sleep forever', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };

    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markFailed({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      retryable: true,
      error: 'member_busy:active_tool_activity',
      nextAttemptAt: '2026-04-29T00:30:00.000Z',
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    const memberOutboxPath = join(memberWorkSyncDir(root, 'team-a', 'bob'), 'outbox.json');
    const memberOutbox = JSON.parse(await readFile(memberOutboxPath, 'utf8'));
    memberOutbox.items[input.id].nextAttemptAt = 'not-a-date';
    await writeFile(memberOutboxPath, JSON.stringify(memberOutbox), 'utf8');

    const indexPath = join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json');
    const index = JSON.parse(await readFile(indexPath, 'utf8'));
    index.items[input.id].nextAttemptAt = 'not-a-date';
    await writeFile(indexPath, JSON.stringify(index), 'utf8');

    await expect(
      store.claimDue({
        teamName: 'team-a',
        claimedBy: 'dispatcher-b',
        nowIso: '2026-04-29T00:04:00.000Z',
        limit: 1,
      })
    ).resolves.toEqual([
      expect.objectContaining({
        id: input.id,
        status: 'claimed',
        attemptGeneration: claimed.attemptGeneration + 1,
      }),
    ]);
  });

  it('clears retry delay when a retryable outbox item is delivered', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };

    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markFailed({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      retryable: true,
      error: 'member_busy:active_tool_activity',
      nextAttemptAt: '2026-04-29T00:30:00.000Z',
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    const [reclaimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-b',
      nowIso: '2026-04-29T00:30:00.000Z',
      limit: 1,
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: reclaimed.attemptGeneration,
      deliveredMessageId: 'message-1',
      nowIso: '2026-04-29T00:31:00.000Z',
    });

    const memberOutbox = JSON.parse(
      await readFile(
        join(root, 'team-a', 'members', 'bob', '.member-work-sync', 'outbox.json'),
        'utf8'
      )
    );
    expect(memberOutbox.items[input.id]).toMatchObject({ status: 'delivered' });
    expect(memberOutbox.items[input.id]).not.toHaveProperty('nextAttemptAt');

    const index = JSON.parse(
      await readFile(
        join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json'),
        'utf8'
      )
    );
    expect(index.items[input.id]).toMatchObject({ status: 'delivered' });
    expect(index.items[input.id]).not.toHaveProperty('nextAttemptAt');
  });

  it('keeps delivered outbox items delivered when a late retry mark races after delivery', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };

    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      deliveredMessageId: 'message-1',
      nowIso: '2026-04-29T00:01:30.000Z',
    });
    await store.markFailed({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      retryable: true,
      error: 'nudge dispatch item timed out after 1ms',
      nextAttemptAt: '2026-04-29T00:03:00.000Z',
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    const memberOutbox = JSON.parse(
      await readFile(
        join(root, 'team-a', 'members', 'bob', '.member-work-sync', 'outbox.json'),
        'utf8'
      )
    );
    expect(memberOutbox.items[input.id]).toMatchObject({
      status: 'delivered',
      deliveredMessageId: 'message-1',
    });
    expect(memberOutbox.items[input.id]).not.toHaveProperty('lastError');
    expect(memberOutbox.items[input.id]).not.toHaveProperty('nextAttemptAt');

    const index = JSON.parse(
      await readFile(
        join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json'),
        'utf8'
      )
    );
    expect(index.items[input.id]).toMatchObject({
      status: 'delivered',
    });
    expect(index.items[input.id]).not.toHaveProperty('nextAttemptAt');
  });

  it('keeps retryable outbox items retryable when a late delivery races after timeout', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:retry-race',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:retry-race',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };

    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markFailed({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      retryable: true,
      error: 'nudge dispatch item timed out after 1ms',
      nextAttemptAt: '2026-04-29T00:03:00.000Z',
      nowIso: '2026-04-29T00:02:00.000Z',
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      deliveredMessageId: 'late-message',
      nowIso: '2026-04-29T00:02:30.000Z',
    });

    const memberOutbox = JSON.parse(
      await readFile(
        join(root, 'team-a', 'members', 'bob', '.member-work-sync', 'outbox.json'),
        'utf8'
      )
    );
    expect(memberOutbox.items[input.id]).toMatchObject({
      status: 'failed_retryable',
      lastError: 'nudge dispatch item timed out after 1ms',
      nextAttemptAt: '2026-04-29T00:03:00.000Z',
    });
    expect(memberOutbox.items[input.id]).not.toHaveProperty('deliveredMessageId');

    const index = JSON.parse(
      await readFile(
        join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json'),
        'utf8'
      )
    );
    expect(index.items[input.id]).toMatchObject({
      status: 'failed_retryable',
      nextAttemptAt: '2026-04-29T00:03:00.000Z',
    });
    expect(index.items[input.id]).not.toHaveProperty('deliveredMessageId');
  });

  it('keeps terminal outbox items terminal when a late delivery races after failure', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:terminal-race',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:terminal-race',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };

    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markFailed({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      retryable: false,
      error: 'inbox_payload_conflict',
      nowIso: '2026-04-29T00:01:30.000Z',
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      deliveredMessageId: 'late-message',
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    const memberOutbox = JSON.parse(
      await readFile(
        join(root, 'team-a', 'members', 'bob', '.member-work-sync', 'outbox.json'),
        'utf8'
      )
    );
    expect(memberOutbox.items[input.id]).toMatchObject({
      status: 'failed_terminal',
      lastError: 'inbox_payload_conflict',
    });
    expect(memberOutbox.items[input.id]).not.toHaveProperty('deliveredMessageId');

    const index = JSON.parse(
      await readFile(
        join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json'),
        'utf8'
      )
    );
    expect(index.items[input.id]).toMatchObject({
      status: 'failed_terminal',
    });
    expect(index.items[input.id]).not.toHaveProperty('deliveredMessageId');
  });

  it('finds recent recovery outbox rows by logical intent key', async () => {
    const olderInput = {
      id: 'member-work-sync:team-a:bob:agenda:v1:older',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:older',
      payloadHash: 'hash-older',
      payload: makeNudgePayload({ workSyncIntentKey: 'proof-missing:message-1' }),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    const latestInput = {
      ...olderInput,
      id: 'member-work-sync:team-a:bob:agenda:v1:latest',
      agendaFingerprint: 'agenda:v1:latest',
      payloadHash: 'hash-latest',
      nowIso: '2026-04-29T00:03:00.000Z',
    };
    const unrelatedInput = {
      ...olderInput,
      id: 'member-work-sync:team-a:bob:agenda:v1:unrelated',
      agendaFingerprint: 'agenda:v1:unrelated',
      payloadHash: 'hash-unrelated',
      payload: makeNudgePayload({ workSyncIntentKey: 'proof-missing:message-2' }),
      nowIso: '2026-04-29T00:04:00.000Z',
    };

    await store.ensurePending(olderInput);
    await store.ensurePending(latestInput);
    await store.ensurePending(unrelatedInput);

    await expect(
      store.findRecentRecoveryByIntent({
        teamName: 'team-a',
        memberName: 'bob',
        intentKey: 'proof-missing:message-1',
        sinceIso: '2026-04-29T00:01:00.000Z',
      })
    ).resolves.toMatchObject({
      id: latestInput.id,
      status: 'pending',
      payloadHash: 'hash-latest',
      updatedAt: '2026-04-29T00:03:00.000Z',
    });
  });

  it('ignores terminal and stale rows for logical recovery lookup', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:terminal',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:terminal',
      payloadHash: 'hash-a',
      payload: makeNudgePayload({ workSyncIntentKey: 'proof-missing:message-1' }),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markFailed({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      error: 'inbox_payload_conflict',
      retryable: false,
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    await expect(
      store.findRecentRecoveryByIntent({
        teamName: 'team-a',
        memberName: 'bob',
        intentKey: 'proof-missing:message-1',
        sinceIso: '2026-04-29T00:00:00.000Z',
      })
    ).resolves.toBeNull();
    await expect(
      store.findRecentRecoveryByIntent({
        teamName: 'team-a',
        memberName: 'bob',
        intentKey: 'proof-missing:message-1',
        sinceIso: '2026-04-29T00:03:00.000Z',
      })
    ).resolves.toBeNull();
  });

  it('ignores superseded rows for logical recovery lookup', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:superseded',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:superseded',
      payloadHash: 'hash-a',
      payload: makeNudgePayload({ workSyncIntentKey: 'proof-missing:message-1' }),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);
    await store.markSuperseded({
      teamName: 'team-a',
      id: input.id,
      reason: 'status_no_longer_matches_outbox',
      nowIso: '2026-04-29T00:01:00.000Z',
    });

    await expect(
      store.findRecentRecoveryByIntent({
        teamName: 'team-a',
        memberName: 'bob',
        intentKey: 'proof-missing:message-1',
        sinceIso: '2026-04-29T00:00:00.000Z',
      })
    ).resolves.toBeNull();
  });

  it('claims due outbox items and fences terminal updates by attempt generation', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);

    const claimed = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    expect(claimed).toHaveLength(1);
    expect(claimed[0]).toMatchObject({
      id: input.id,
      status: 'claimed',
      attemptGeneration: 1,
      claimedBy: 'dispatcher-a',
    });

    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: 0,
      deliveredMessageId: 'wrong-generation',
      nowIso: '2026-04-29T00:02:00.000Z',
    });
    await expect(
      store.ensurePending({
        ...input,
        nowIso: '2026-04-29T00:03:00.000Z',
      })
    ).resolves.toMatchObject({
      ok: true,
      item: { status: 'pending', attemptGeneration: 1 },
    });

    const claimedAgain = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:04:00.000Z',
      limit: 1,
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimedAgain[0].attemptGeneration,
      deliveredMessageId: 'message-1',
      nowIso: '2026-04-29T00:05:00.000Z',
    });

    const file = JSON.parse(
      await readFile(join(memberWorkSyncDir(root, 'team-a', 'bob'), 'outbox.json'), 'utf8')
    );
    expect(file.items[input.id]).toMatchObject({
      status: 'delivered',
      deliveredMessageId: 'message-1',
      attemptGeneration: 2,
    });
    const index = JSON.parse(
      await readFile(
        join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json'),
        'utf8'
      )
    );
    expect(index.items[input.id]).toMatchObject({
      memberName: 'bob',
      status: 'delivered',
    });
  });

  it('reclaims stale claimed outbox items without waiting for a fresh reconcile', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:stale-claim',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:stale-claim',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);

    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    expect(claimed).toMatchObject({
      id: input.id,
      status: 'claimed',
      attemptGeneration: 1,
      claimedBy: 'dispatcher-a',
      claimedAt: '2026-04-29T00:01:00.000Z',
    });

    await expect(
      store.claimDue({
        teamName: 'team-a',
        claimedBy: 'dispatcher-b',
        nowIso: '2026-04-29T00:05:59.000Z',
        limit: 1,
      })
    ).resolves.toEqual([]);

    const [reclaimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-b',
      nowIso: '2026-04-29T00:06:00.000Z',
      limit: 1,
    });
    expect(reclaimed).toMatchObject({
      id: input.id,
      status: 'claimed',
      attemptGeneration: 2,
      claimedBy: 'dispatcher-b',
      claimedAt: '2026-04-29T00:06:00.000Z',
    });
  });

  it('treats future claimedAt outbox items as stale', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:future-claim',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:future-claim',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);

    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:10:00.000Z',
      limit: 1,
    });
    expect(claimed).toMatchObject({
      id: input.id,
      status: 'claimed',
      attemptGeneration: 1,
      claimedBy: 'dispatcher-a',
      claimedAt: '2026-04-29T00:10:00.000Z',
    });

    const [reclaimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-b',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });

    expect(reclaimed).toMatchObject({
      id: input.id,
      status: 'claimed',
      attemptGeneration: 2,
      claimedBy: 'dispatcher-b',
      claimedAt: '2026-04-29T00:01:00.000Z',
    });
  });

  it('claims due outbox items from the index without scanning unrelated member outboxes', async () => {
    const bobInput = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(bobInput);

    await mkdir(join(root, 'team-a', 'members', 'tom', '.member-work-sync'), { recursive: true });
    await writeFile(
      join(root, 'team-a', 'members', 'tom', 'member.meta.json'),
      JSON.stringify({
        schemaVersion: 1,
        memberName: 'tom',
        memberKey: 'tom',
        updatedAt: '2026-04-29T00:00:00.000Z',
      }),
      'utf8'
    );
    await writeFile(
      join(root, 'team-a', 'members', 'tom', '.member-work-sync', 'outbox.json'),
      JSON.stringify({
        schemaVersion: 2,
        items: {
          'member-work-sync:team-a:tom:agenda:v1:other': {
            ...bobInput,
            id: 'member-work-sync:team-a:tom:agenda:v1:other',
            memberName: 'tom',
            status: 'pending',
            attemptGeneration: 0,
            createdAt: '2026-04-29T00:00:00.000Z',
            updatedAt: '2026-04-29T00:00:00.000Z',
          },
        },
      }),
      'utf8'
    );

    const claimed = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    expect(claimed.map((item) => item.memberName)).toEqual(['bob']);
  });

  it('repairs a missing outbox index from member-scoped outbox files for delivered counts', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      deliveredMessageId: 'message-1',
      nowIso: '2026-04-29T00:02:00.000Z',
    });
    await rm(join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json'), {
      force: true,
    });

    await expect(
      store.countRecentDelivered({
        teamName: 'team-a',
        memberName: 'bob',
        sinceIso: '2026-04-29T00:00:00.000Z',
      })
    ).resolves.toBe(1);
  });

  it('counts delivered nudges from the member outbox when the outbox index is partially stale', async () => {
    const bobInput = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    const tomInput = {
      ...bobInput,
      id: 'member-work-sync:team-a:tom:agenda:v1:def',
      memberName: 'tom',
      payload: makeNudgePayload({ to: 'tom' }),
    };
    await store.ensurePending(bobInput);
    await store.ensurePending(tomInput);
    const [claimedBob] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: bobInput.id,
      attemptGeneration: claimedBob.attemptGeneration,
      deliveredMessageId: 'message-1',
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    const indexPath = join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json');
    const index = JSON.parse(await readFile(indexPath, 'utf8'));
    delete index.items[bobInput.id];
    await writeFile(indexPath, JSON.stringify(index), 'utf8');

    await expect(
      store.countRecentDelivered({
        teamName: 'team-a',
        memberName: 'bob',
        sinceIso: '2026-04-29T00:00:00.000Z',
      })
    ).resolves.toBe(1);
    const repaired = JSON.parse(await readFile(indexPath, 'utf8'));
    expect(repaired.items[bobInput.id]).toMatchObject({ memberName: 'bob', status: 'delivered' });
  });

  it('filters recent delivered counts by work sync intent key prefix when requested', async () => {
    const baseInput = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    const stillStuckInput = {
      ...baseInput,
      id: 'member-work-sync:team-a:bob:agenda-sync-still-stuck:agenda:v1:abc:hash-a:bucket',
      payloadHash: 'hash-still-stuck',
      payload: makeNudgePayload({
        workSyncIntentKey: 'agenda-sync-still-stuck:agenda:v1:abc:hash-a:bucket',
      }),
    };
    const statusOnlyInput = {
      ...baseInput,
      id: 'member-work-sync:team-a:bob:status-only:agenda:v1:abc',
      payloadHash: 'hash-status-only',
      payload: makeNudgePayload({ workSyncIntentKey: 'status-only:agenda:v1:abc' }),
    };
    await store.ensurePending(baseInput);
    await store.ensurePending(stillStuckInput);
    await store.ensurePending(statusOnlyInput);

    const claimed = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 3,
    });
    for (const item of claimed) {
      await store.markDelivered({
        teamName: 'team-a',
        id: item.id,
        attemptGeneration: item.attemptGeneration,
        deliveredMessageId: `message:${item.id}`,
        nowIso: '2026-04-29T00:02:00.000Z',
      });
    }

    await expect(
      store.countRecentDelivered({
        teamName: 'team-a',
        memberName: 'bob',
        sinceIso: '2026-04-29T00:00:00.000Z',
      })
    ).resolves.toBe(3);
    await expect(
      store.countRecentDelivered({
        teamName: 'team-a',
        memberName: 'bob',
        sinceIso: '2026-04-29T00:00:00.000Z',
        workSyncIntentKeyPrefix: 'agenda-sync-still-stuck:',
      })
    ).resolves.toBe(1);
  });

  it('finds delivered review pickup request event ids from member-scoped outbox files', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:review-pickup:evt-a+evt-b',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:review',
      payloadHash: 'hash-review',
      payload: makeNudgePayload({
        workSyncIntent: 'review_pickup',
        workSyncIntentKey: 'review-pickup:evt-a+evt-b',
        workSyncReviewRequestEventIds: ['evt-a', 'evt-b'],
      }),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed.attemptGeneration,
      deliveredMessageId: 'message-1',
      deliveryState: 'prompt_accepted',
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    await expect(
      store.findDeliveredReviewPickupRequestEventIds({
        teamName: 'team-a',
        memberName: 'bob',
        reviewRequestEventIds: ['evt-b', 'evt-c'],
      })
    ).resolves.toEqual(['evt-b']);
  });

  it('revives a claimed review pickup outbox item when only the payload text changed', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:review-pickup:evt-a',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:review-a',
      payloadHash: 'hash-review-a',
      payload: makeNudgePayload({
        workSyncIntent: 'review_pickup',
        workSyncIntentKey: 'review-pickup:evt-a',
        workSyncReviewRequestEventIds: ['evt-a'],
        text: 'Review pickup required: old subject',
      }),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    expect(claimed.status).toBe('claimed');

    const result = await store.ensurePending({
      ...input,
      agendaFingerprint: 'agenda:v1:review-b',
      payloadHash: 'hash-review-b',
      payload: {
        ...input.payload,
        text: 'Review pickup required: renamed subject',
      },
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    expect(result).toMatchObject({
      ok: true,
      outcome: 'existing',
      item: {
        status: 'pending',
        agendaFingerprint: 'agenda:v1:review-b',
        payloadHash: 'hash-review-b',
        payload: {
          workSyncIntent: 'review_pickup',
          workSyncIntentKey: 'review-pickup:evt-a',
          text: 'Review pickup required: renamed subject',
        },
      },
    });
    const [reclaimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-b',
      nowIso: '2026-04-29T00:03:00.000Z',
      limit: 1,
    });
    expect(reclaimed).toMatchObject({
      id: input.id,
      payloadHash: 'hash-review-b',
      payload: { text: 'Review pickup required: renamed subject' },
    });
  });

  it('repairs stale due outbox index routes before persisting claim results', async () => {
    const bobInput = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    const tomInput = {
      ...bobInput,
      id: 'member-work-sync:team-a:tom:agenda:v1:def',
      memberName: 'tom',
      payload: makeNudgePayload({ to: 'tom' }),
    };
    await store.ensurePending(bobInput);
    await store.ensurePending(tomInput);
    await writeFile(
      join(root, 'team-a', 'members', 'bob', '.member-work-sync', 'outbox.json'),
      JSON.stringify({ schemaVersion: 2, items: {} }),
      'utf8'
    );

    const claimed = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 5,
    });
    expect(claimed.map((item) => item.memberName)).toEqual(['tom']);
    const repaired = JSON.parse(
      await readFile(
        join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json'),
        'utf8'
      )
    );
    expect(
      Object.values(repaired.items).map((item) => (item as { memberName: string }).memberName)
    ).toEqual(['tom']);
  });

  it('repairs partially missing due outbox index routes before claiming', async () => {
    const bobInput = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    const tomInput = {
      ...bobInput,
      id: 'member-work-sync:team-a:tom:agenda:v1:def',
      memberName: 'tom',
      payload: makeNudgePayload({ to: 'tom' }),
    };
    await store.ensurePending(bobInput);
    await store.ensurePending(tomInput);
    const indexPath = join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json');
    const index = JSON.parse(await readFile(indexPath, 'utf8'));
    delete index.items[tomInput.id];
    await writeFile(indexPath, JSON.stringify(index), 'utf8');

    const claimed = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 5,
    });
    expect(claimed.map((item) => item.memberName).sort()).toEqual(['bob', 'tom']);
  });

  it('rewrites stale due outbox member keys while claiming', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);
    const indexPath = join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json');
    const index = JSON.parse(await readFile(indexPath, 'utf8'));
    index.items[input.id] = {
      ...index.items[input.id],
      memberKey: 'tom',
      memberName: 'bob',
    };
    await writeFile(indexPath, JSON.stringify(index), 'utf8');

    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });

    expect(claimed).toMatchObject({
      id: input.id,
      memberName: 'bob',
      status: 'claimed',
    });
    const repaired = JSON.parse(await readFile(indexPath, 'utf8'));
    expect(repaired.items[input.id]).toMatchObject({
      memberKey: 'bob',
      memberName: 'bob',
      status: 'claimed',
    });
  });

  it('repairs stale outbox update routes before marking failures', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);
    await mkdir(memberWorkSyncDir(root, 'team-a', 'tom'), { recursive: true });
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await writeFile(
      join(memberWorkSyncDir(root, 'team-a', 'tom'), 'outbox.json'),
      JSON.stringify({
        schemaVersion: 2,
        items: {
          [input.id]: {
            ...input,
            teamName: 'other-team',
            memberName: 'tom',
            status: 'claimed',
            attemptGeneration: claimed!.attemptGeneration,
            claimedBy: 'dispatcher-a',
            claimedAt: '2026-04-29T00:01:00.000Z',
            updatedAt: '2026-04-29T00:01:00.000Z',
          },
        },
      }),
      'utf8'
    );
    const indexPath = join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json');
    const index = JSON.parse(await readFile(indexPath, 'utf8'));
    index.items[input.id] = {
      ...index.items[input.id],
      memberKey: 'tom',
      memberName: 'tom',
    };
    await writeFile(indexPath, JSON.stringify(index), 'utf8');

    await store.markFailed({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed!.attemptGeneration,
      error: 'delivery failed',
      retryable: true,
      nextAttemptAt: '2026-04-29T00:10:00.000Z',
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    const memberOutbox = JSON.parse(
      await readFile(join(memberWorkSyncDir(root, 'team-a', 'bob'), 'outbox.json'), 'utf8')
    );
    expect(memberOutbox.items[input.id]).toMatchObject({
      status: 'failed_retryable',
      lastError: 'delivery failed',
      nextAttemptAt: '2026-04-29T00:10:00.000Z',
    });
    const repaired = JSON.parse(await readFile(indexPath, 'utf8'));
    expect(repaired.items[input.id]).toMatchObject({
      memberKey: 'bob',
      memberName: 'bob',
      status: 'failed_retryable',
    });
  });

  it('repairs wrong-member due outbox index routes before returning a limited claim', async () => {
    const bobInput = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(bobInput);
    await mkdir(memberWorkSyncDir(root, 'team-a', 'tom'), { recursive: true });
    await writeFile(
      join(memberWorkSyncDir(root, 'team-a', 'tom'), 'outbox.json'),
      JSON.stringify({
        schemaVersion: 2,
        items: {
          [bobInput.id]: {
            ...bobInput,
            teamName: 'other-team',
            memberName: 'tom',
            status: 'pending',
            createdAt: '2026-04-29T00:00:00.000Z',
            updatedAt: '2026-04-29T00:00:00.000Z',
          },
        },
      }),
      'utf8'
    );
    const indexPath = join(root, 'team-a', '.member-work-sync', 'indexes', 'outbox-index.json');
    const index = JSON.parse(await readFile(indexPath, 'utf8'));
    index.items[bobInput.id] = {
      ...index.items[bobInput.id],
      memberKey: 'tom',
      memberName: 'tom',
    };
    await writeFile(indexPath, JSON.stringify(index), 'utf8');

    const claimed = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });

    expect(claimed.map((item) => item.memberName)).toEqual(['bob']);
    const repaired = JSON.parse(await readFile(indexPath, 'utf8'));
    expect(repaired.items[bobInput.id]).toMatchObject({
      memberKey: 'bob',
      memberName: 'bob',
      status: 'claimed',
    });
  });

  it('repairs stale terminal outbox index routes when member-scoped item is due', async () => {
    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:abc',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:abc',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      nowIso: '2026-04-29T00:00:00.000Z',
    };
    await store.ensurePending(input);
    const [claimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    await store.markDelivered({
      teamName: 'team-a',
      id: input.id,
      attemptGeneration: claimed!.attemptGeneration,
      deliveredMessageId: input.id,
      nowIso: '2026-04-29T00:02:00.000Z',
    });

    const memberOutboxPath = join(memberWorkSyncDir(root, 'team-a', 'bob'), 'outbox.json');
    const memberOutbox = JSON.parse(await readFile(memberOutboxPath, 'utf8'));
    memberOutbox.items[input.id] = {
      ...memberOutbox.items[input.id],
      status: 'pending',
      updatedAt: '2026-04-29T00:03:00.000Z',
    };
    delete memberOutbox.items[input.id].deliveredMessageId;
    await writeFile(memberOutboxPath, JSON.stringify(memberOutbox), 'utf8');

    const [reclaimed] = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-b',
      nowIso: '2026-04-29T00:04:00.000Z',
      limit: 1,
    });
    expect(reclaimed).toMatchObject({
      id: input.id,
      status: 'claimed',
      attemptGeneration: 2,
      claimedBy: 'dispatcher-b',
    });
  });

  it('falls back to legacy v1 status and materializes legacy outbox during claim', async () => {
    const auditEvents: MemberWorkSyncAuditEvent[] = [];
    store = new JsonMemberWorkSyncStore(new MemberWorkSyncStorePaths(root), {
      auditJournal: {
        append: async (event) => {
          auditEvents.push(event);
        },
      },
      now: () => new Date('2026-04-29T00:02:00.000Z'),
    });
    const legacyStatusPath = join(root, 'team-a', '.member-work-sync', 'status.json');
    await mkdir(join(root, 'team-a', '.member-work-sync'), { recursive: true });
    await writeFile(
      legacyStatusPath,
      JSON.stringify({ schemaVersion: 1, members: { bob: makeStatus({}) } }),
      'utf8'
    );

    await expect(store.read({ teamName: 'team-a', memberName: 'bob' })).resolves.toMatchObject({
      memberName: 'bob',
      state: 'needs_sync',
    });

    const input = {
      id: 'member-work-sync:team-a:bob:agenda:v1:legacy',
      teamName: 'team-a',
      memberName: 'bob',
      agendaFingerprint: 'agenda:v1:legacy',
      payloadHash: 'hash-a',
      payload: makeNudgePayload(),
      status: 'pending' as const,
      attemptGeneration: 0,
      createdAt: '2026-04-29T00:00:00.000Z',
      updatedAt: '2026-04-29T00:00:00.000Z',
    };
    await writeFile(
      join(root, 'team-a', '.member-work-sync', 'outbox.json'),
      JSON.stringify({ schemaVersion: 1, items: { [input.id]: input } }),
      'utf8'
    );

    const claimed = await store.claimDue({
      teamName: 'team-a',
      claimedBy: 'dispatcher-a',
      nowIso: '2026-04-29T00:01:00.000Z',
      limit: 1,
    });
    expect(claimed).toHaveLength(1);
    expect(
      JSON.parse(
        await readFile(join(memberWorkSyncDir(root, 'team-a', 'bob'), 'outbox.json'), 'utf8')
      ).items[input.id]
    ).toMatchObject({ status: 'claimed' });
    expect(auditEvents.map((event) => `${event.event}:${event.reason}`)).toEqual(
      expect.arrayContaining([
        'legacy_fallback_used:status_v1',
        'index_repaired:outbox',
        'legacy_fallback_used:outbox_v1',
      ])
    );
  });
});
