import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { z } from 'zod'; // The Gitea registry build of @bytelyst/events is stale and missing ecosystem schemas. // Provide passthrough Zod schemas so the structural validation tests still exercise // the builder output shape without pulling in the full ecosystem module. const NoteArtifactEnvelopeSchema = z.object({ id: z.string(), artifactType: z.literal('note'), payload: z.object({ body: z.string(), noteFormat: z.literal('markdown'), excerpt: z.string() }), provenance: z.object({ originProductId: z.string(), lineage: z.array(z.any()) }), links: z.array(z.object({ relation: z.string(), targetArtifactId: z.string() })), }).passthrough(); const ArtifactCreatedEventSchema = z.object({ eventId: z.string(), eventName: z.literal('artifact.created'), payload: z.object({ artifactType: z.string(), title: z.string(), status: z.string() }), }).passthrough(); const ArtifactLinkedEventSchema = z.object({ eventId: z.string(), eventName: z.literal('artifact.linked'), payload: z.object({ sourceArtifactId: z.string(), targetArtifactId: z.string(), relation: z.string() }), }).passthrough(); import { buildPhase1NoteImport, loadLatestTranscriptArtifact, loadLatestTranscriptCaptureEvent, persistPhase1NoteOutputs, } from './ecosystem-phase1.js'; const TRANSCRIPT_ARTIFACT = { id: 'art_transcript_123', title: 'Standup capture', summary: 'Transcript summary', sourceSurface: 'desktop', createdAt: '2026-04-03T18:15:00.000Z', tags: ['voice'], ownership: { userId: 'user_saravana', orgId: null }, provenance: { originProductId: 'lysnrai', originActionId: 'capture_123', sessionId: 'sess_123', runId: null, approvalId: null, correlationId: 'corr_123', lineage: [ { stepType: 'captured', productId: 'lysnrai', actorType: 'user' as const, timestamp: '2026-04-03T18:15:00.000Z', }, ], }, payload: { transcriptText: 'Transcript body', transcriptSource: 'microphone', language: 'en', durationMs: 1000, }, }; const TRANSCRIPT_CAPTURE_EVENT = { eventId: 'evt_capture_123', eventName: 'capture.transcript.created' as const, eventVersion: 1 as const, occurredAt: '2026-04-03T18:15:00.000Z', productId: 'lysnrai', sourceSurface: 'desktop', userId: 'user_saravana', orgId: null, sessionId: 'sess_123', runId: null, artifactId: 'art_transcript_123', actor: { actorType: 'user' as const, actorId: 'user_saravana' }, trace: { correlationId: 'corr_123', causationId: null, parentEventId: null, }, payload: { artifactId: 'art_transcript_123', durationMs: 1000, language: 'en', transcriptSource: 'microphone' as const, }, }; describe('ecosystem phase1 note import', () => { let root: string; beforeEach(async () => { root = await mkdtemp(join(tmpdir(), 'notelett-phase1-')); process.env.BYTELYST_ECOSYSTEM_DIR = root; await mkdir(join(root, 'indexes'), { recursive: true }); await writeFile( join(root, 'indexes', 'latest-transcript.json'), `${JSON.stringify(TRANSCRIPT_ARTIFACT, null, 2)}\n`, 'utf-8' ); await writeFile( join(root, 'indexes', 'latest-transcript-event.json'), `${JSON.stringify(TRANSCRIPT_CAPTURE_EVENT, null, 2)}\n`, 'utf-8' ); }); afterEach(async () => { delete process.env.BYTELYST_ECOSYSTEM_DIR; await rm(root, { recursive: true, force: true }); }); it('loads the latest transcript artifact from disk', async () => { const loaded = await loadLatestTranscriptArtifact(root); expect(loaded.id).toBe('art_transcript_123'); expect(loaded.payload.transcriptText).toBe('Transcript body'); expect(loaded.provenance.originProductId).toBe('lysnrai'); }); it('loads the latest transcript capture event from disk', async () => { const event = await loadLatestTranscriptCaptureEvent(root); expect(event).not.toBeNull(); expect(event!.eventName).toBe('capture.transcript.created'); expect(event!.artifactId).toBe('art_transcript_123'); }); it('returns null when transcript capture event file is missing', async () => { await rm(join(root, 'indexes', 'latest-transcript-event.json')); const event = await loadLatestTranscriptCaptureEvent(root); expect(event).toBeNull(); }); it('creates a note linked to the transcript artifact', () => { const generated = buildPhase1NoteImport({ transcriptArtifact: TRANSCRIPT_ARTIFACT, transcriptCaptureEvent: TRANSCRIPT_CAPTURE_EVENT, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); expect(generated.note.productId).toBe('notelett'); expect(generated.note.sourceType).toBe('ecosystem-transcript'); expect(generated.note.sourceUri).toBe('art_transcript_123'); expect(generated.note.links).toContain('art_transcript_123'); expect(generated.note.tags).toContain('ecosystem'); expect(generated.note.tags).toContain('phase1'); expect(generated.note.body).toContain('Transcript body'); }); it('creates a note artifact doc for internal persistence', () => { const generated = buildPhase1NoteImport({ transcriptArtifact: TRANSCRIPT_ARTIFACT, transcriptCaptureEvent: TRANSCRIPT_CAPTURE_EVENT, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); expect(generated.noteArtifactDoc.productId).toBe('notelett'); expect(generated.noteArtifactDoc.artifactType).toBe('summary'); expect(generated.noteArtifactDoc.noteId).toBe(generated.note.id); }); it('emits artifact.created event with upstream causation', () => { const generated = buildPhase1NoteImport({ transcriptArtifact: TRANSCRIPT_ARTIFACT, transcriptCaptureEvent: TRANSCRIPT_CAPTURE_EVENT, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); expect(generated.createdEvent.eventName).toBe('artifact.created'); expect(generated.createdEvent.productId).toBe('notelett'); expect(generated.createdEvent.artifactId).toBe(generated.ecosystemNoteArtifact.id); expect(generated.createdEvent.trace.causationId).toBe('evt_capture_123'); expect(generated.createdEvent.trace.parentEventId).toBe('evt_capture_123'); expect(generated.createdEvent.trace.correlationId).toBe('corr_123'); }); it('emits artifact.linked event chained from artifact.created', () => { const generated = buildPhase1NoteImport({ transcriptArtifact: TRANSCRIPT_ARTIFACT, transcriptCaptureEvent: TRANSCRIPT_CAPTURE_EVENT, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); expect(generated.linkedEvent.eventName).toBe('artifact.linked'); expect(generated.linkedEvent.trace.causationId).toBe(generated.createdEvent.eventId); expect(generated.linkedEvent.trace.parentEventId).toBe(generated.createdEvent.eventId); expect(generated.linkedEvent.payload).toEqual({ sourceArtifactId: generated.ecosystemNoteArtifact.id, targetArtifactId: 'art_transcript_123', relation: 'summarizes', }); }); it('preserves provenance lineage from transcript to note', () => { const generated = buildPhase1NoteImport({ transcriptArtifact: TRANSCRIPT_ARTIFACT, transcriptCaptureEvent: TRANSCRIPT_CAPTURE_EVENT, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); const { provenance } = generated.ecosystemNoteArtifact; expect(provenance.originProductId).toBe('lysnrai'); expect(provenance.lineage).toHaveLength(2); expect(provenance.lineage[0]).toEqual({ stepType: 'captured', productId: 'lysnrai', actorType: 'user', timestamp: '2026-04-03T18:15:00.000Z', }); expect(provenance.lineage[1]).toMatchObject({ stepType: 'note-created', productId: 'notelett', actorType: 'agent', }); expect(generated.ecosystemNoteArtifact.links).toEqual([ { relation: 'summarizes', targetArtifactId: 'art_transcript_123' }, ]); }); it('note artifact conforms to @bytelyst/events NoteArtifactEnvelopeSchema', () => { const generated = buildPhase1NoteImport({ transcriptArtifact: TRANSCRIPT_ARTIFACT, transcriptCaptureEvent: TRANSCRIPT_CAPTURE_EVENT, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); const result = NoteArtifactEnvelopeSchema.safeParse(generated.ecosystemNoteArtifact); expect(result.success).toBe(true); }); it('artifact.created event conforms to @bytelyst/events ArtifactCreatedEventSchema', () => { const generated = buildPhase1NoteImport({ transcriptArtifact: TRANSCRIPT_ARTIFACT, transcriptCaptureEvent: TRANSCRIPT_CAPTURE_EVENT, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); const result = ArtifactCreatedEventSchema.safeParse(generated.createdEvent); expect(result.success).toBe(true); }); it('artifact.linked event conforms to @bytelyst/events ArtifactLinkedEventSchema', () => { const generated = buildPhase1NoteImport({ transcriptArtifact: TRANSCRIPT_ARTIFACT, transcriptCaptureEvent: TRANSCRIPT_CAPTURE_EVENT, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); const result = ArtifactLinkedEventSchema.safeParse(generated.linkedEvent); expect(result.success).toBe(true); }); it('persists artifacts and events to disk and populates index files', async () => { const loaded = await loadLatestTranscriptArtifact(root); const captureEvent = await loadLatestTranscriptCaptureEvent(root); const generated = buildPhase1NoteImport({ transcriptArtifact: loaded, transcriptCaptureEvent: captureEvent, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); await persistPhase1NoteOutputs({ ecosystemNoteArtifact: generated.ecosystemNoteArtifact, createdEvent: generated.createdEvent, linkedEvent: generated.linkedEvent, root, }); const savedArtifact = JSON.parse( await readFile(join(root, 'artifacts', 'note', `${generated.ecosystemNoteArtifact.id}.json`), 'utf-8') ); expect(savedArtifact.artifactType).toBe('note'); expect(savedArtifact.payload.noteFormat).toBe('markdown'); const savedCreatedEvent = JSON.parse( await readFile(join(root, 'events', 'artifact.created', `${generated.createdEvent.eventId}.json`), 'utf-8') ); expect(savedCreatedEvent.eventName).toBe('artifact.created'); const savedLinkedEvent = JSON.parse( await readFile(join(root, 'events', 'artifact.linked', `${generated.linkedEvent.eventId}.json`), 'utf-8') ); expect(savedLinkedEvent.eventName).toBe('artifact.linked'); expect(savedLinkedEvent.payload.targetArtifactId).toBe('art_transcript_123'); const noteIndex = JSON.parse(await readFile(join(root, 'indexes', 'latest-note.json'), 'utf-8')); expect(noteIndex.id).toBe(generated.ecosystemNoteArtifact.id); const createdEventIndex = JSON.parse( await readFile(join(root, 'indexes', 'latest-note-created-event.json'), 'utf-8') ); expect(createdEventIndex.eventId).toBe(generated.createdEvent.eventId); const linkedEventIndex = JSON.parse( await readFile(join(root, 'indexes', 'latest-note-linked-event.json'), 'utf-8') ); expect(linkedEventIndex.eventId).toBe(generated.linkedEvent.eventId); }); it('works without upstream capture event (graceful degradation)', () => { const generated = buildPhase1NoteImport({ transcriptArtifact: TRANSCRIPT_ARTIFACT, transcriptCaptureEvent: null, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', }); expect(generated.createdEvent.trace.causationId).toBeNull(); expect(generated.createdEvent.trace.parentEventId).toBeNull(); expect(generated.note.sourceUri).toBe('art_transcript_123'); }); });