import { Command } from 'commander'; import { describe, expect, it, vi } from 'vitest'; import type { FleetRoster } from './fleet.js'; import { TmuxPromotionTransport } from '../fleet/promotion-transport.js'; import { promoteSeat, registerPromoteCommand, type PromotionBreadcrumbStore, type PromotionResult, type PromotionTransport, } from './promote.js'; const attemptId = 'a'.repeat(64); const target = { bundle: 'local', seat: 'claude-seat', sessionId: 'b'.repeat(64), }; function transport(): PromotionTransport { return { resolve: vi.fn(async () => target), sendPromotion: vi.fn(async () => {}), }; } describe('mosaic promote', () => { it('accepts only a fresh result correlated to this attempt', async () => { const promotionTransport = transport(); const store: PromotionBreadcrumbStore = { readAttemptId: vi.fn().mockResolvedValueOnce(null).mockResolvedValue(attemptId), readResult: vi .fn() .mockResolvedValueOnce({ attempt_id: 'c'.repeat(64), expires_at_wallclock: 4_600, reason: null, session_id: target.sessionId, ts: 1_001, verified: true, }) .mockResolvedValueOnce({ attempt_id: attemptId, expires_at_wallclock: 4_600, reason: null, session_id: target.sessionId, ts: 1_001, verified: true, }), }; const result = await promoteSeat('claude-seat', { clock: () => 1_000, sleep: async () => {}, store, timeoutMs: 1, transport: promotionTransport, }); expect(result).toEqual({ bundle: 'local', expiresAtWallclock: 4_600, reason: null, seat: 'claude-seat', sessionId: 'b'.repeat(64), status: 'VERIFIED', }); expect(promotionTransport.sendPromotion).toHaveBeenCalledWith(target); expect(store.readResult).toHaveBeenCalledTimes(2); }); it('accepts a fresh result for this session when completion consumed the pending nonce', async () => { const promotionTransport = transport(); let now = 1_000; const store: PromotionBreadcrumbStore = { readAttemptId: vi.fn(async () => null), readResult: vi.fn(async () => ({ attempt_id: attemptId, expires_at_wallclock: 4_600, reason: null, session_id: target.sessionId, ts: 1_001, verified: true, })), }; const result = await promoteSeat('claude-seat', { clock: () => now, sleep: async () => { now += 10; }, store, timeoutMs: 10, transport: promotionTransport, }); expect(result.status).toBe('VERIFIED'); }); it('returns UNVERIFIED within the command bound when a tmux runner wedges', async () => { vi.useFakeTimers(); const roster: FleetRoster = { agents: [{ className: 'worker', name: 'claude-seat', runtime: 'claude' }], defaults: { workingDirectory: '~/src' }, runtimes: {}, tmux: { holderSession: '_holder', socketName: 'mosaic-fleet' }, transport: 'tmux', version: 1, }; const promotionTransport = new TmuxPromotionTransport({ mosaicHome: '/mosaic', rosterLoader: async () => roster, runner: async () => new Promise(() => {}), }); const store: PromotionBreadcrumbStore = { readAttemptId: vi.fn(async () => null), readResult: vi.fn(async () => null), }; try { let observedResult: PromotionResult | undefined; void promoteSeat('claude-seat', { store, transport: promotionTransport, }).then((result) => { observedResult = result; }); await vi.advanceTimersByTimeAsync(5_000); expect(observedResult).toMatchObject({ reason: 'RESOLVE_FAILED: Promotion transport command timed out after 5000ms.', seat: 'claude-seat', status: 'UNVERIFIED', }); } finally { vi.useRealTimers(); } }); it('prints VERIFIED with the resolved seat, session, bundle, and wall-clock expiry', async () => { const promotionTransport = transport(); const store: PromotionBreadcrumbStore = { readAttemptId: vi.fn().mockResolvedValueOnce(null).mockResolvedValue(attemptId), readResult: vi.fn(async () => ({ attempt_id: attemptId, expires_at_wallclock: 4_600, reason: null, session_id: target.sessionId, ts: Number.MAX_SAFE_INTEGER, verified: true, })), }; const output = vi.spyOn(console, 'log').mockImplementation(() => {}); const program = new Command().exitOverride(); registerPromoteCommand(program, { mintAuthorization: async () => {}, store, transport: promotionTransport, }); try { await program.parseAsync(['node', 'mosaic', 'promote', 'claude-seat']); expect(output).toHaveBeenCalledWith( `VERIFIED seat=claude-seat session=${target.sessionId} bundle=local expiry=1970-01-01T01:16:40.000Z`, ); expect(process.exitCode).not.toBe(1); } finally { output.mockRestore(); process.exitCode = undefined; } }); it('prints UNVERIFIED and exits 1 when delivery fails', async () => { const promotionTransport: PromotionTransport = { resolve: vi.fn(async () => target), sendPromotion: vi.fn(async () => { throw new Error('tmux unavailable'); }), }; const store: PromotionBreadcrumbStore = { readAttemptId: vi.fn(async () => null), readResult: vi.fn(async () => null), }; const output = vi.spyOn(console, 'log').mockImplementation(() => {}); const program = new Command().exitOverride(); registerPromoteCommand(program, { mintAuthorization: async () => {}, store, transport: promotionTransport, }); try { process.exitCode = undefined; await program.parseAsync(['node', 'mosaic', 'promote', 'claude-seat']); expect(output).toHaveBeenCalledWith( `UNVERIFIED seat=claude-seat session=${target.sessionId} bundle=local expiry=none reason=DELIVERY_FAILED: tmux unavailable`, ); expect(process.exitCode).toBe(1); } finally { output.mockRestore(); process.exitCode = undefined; } }); it('rejects a stale result even when its nonce matches', async () => { const promotionTransport = transport(); let now = 1_000; const store: PromotionBreadcrumbStore = { readAttemptId: vi.fn().mockResolvedValueOnce(null).mockResolvedValue(attemptId), readResult: vi.fn(async () => ({ attempt_id: attemptId, expires_at_wallclock: 4_600, reason: null, session_id: target.sessionId, ts: 1_000, verified: true, })), }; const result = await promoteSeat('claude-seat', { clock: () => now, sleep: async () => { now += 10; }, store, timeoutMs: 10, transport: promotionTransport, }); expect(result).toEqual({ bundle: 'local', expiresAtWallclock: null, reason: 'PROMOTION_TIMEOUT', seat: 'claude-seat', sessionId: 'b'.repeat(64), status: 'UNVERIFIED', }); }); it('does not accept a result for a pending attempt that existed before send', async () => { const promotionTransport = transport(); let now = 1_000; const store: PromotionBreadcrumbStore = { readAttemptId: vi.fn(async () => attemptId), readResult: vi.fn(async () => ({ attempt_id: attemptId, expires_at_wallclock: 4_600, reason: null, session_id: target.sessionId, ts: 1_001, verified: true, })), }; const result = await promoteSeat('claude-seat', { clock: () => now, sleep: async () => { now += 10; }, store, timeoutMs: 10, transport: promotionTransport, }); expect(result.status).toBe('UNVERIFIED'); expect(store.readResult).not.toHaveBeenCalled(); }); it('returns UNVERIFIED after a bounded timeout without reading stdin', async () => { const promotionTransport = transport(); let now = 1_000; const store: PromotionBreadcrumbStore = { readAttemptId: vi.fn(async () => null), readResult: vi.fn(async () => null), }; const result = await promoteSeat('claude-seat', { clock: () => now, sleep: async () => { now += 10; }, store, timeoutMs: 10, transport: promotionTransport, }); expect(result).toEqual({ bundle: 'local', expiresAtWallclock: null, reason: 'PROMOTION_TIMEOUT', seat: 'claude-seat', sessionId: 'b'.repeat(64), status: 'UNVERIFIED', }); expect(promotionTransport.sendPromotion).toHaveBeenCalledOnce(); expect(store.readResult).toHaveBeenCalledTimes(2); }); });