diff --git a/backend/src/lib/ecosystem-phase1.test.ts b/backend/src/lib/ecosystem-phase1.test.ts index b8d0990..e5da1b5 100644 --- a/backend/src/lib/ecosystem-phase1.test.ts +++ b/backend/src/lib/ecosystem-phase1.test.ts @@ -5,6 +5,7 @@ import { join } from 'node:path'; import { buildPhase1NoteImport, loadLatestTranscriptArtifact, + loadLatestTranscriptCaptureEvent, persistPhase1NoteOutputs, } from './ecosystem-phase1.js'; @@ -55,10 +56,45 @@ describe('ecosystem phase1 note import', () => { `${JSON.stringify(transcriptArtifact, null, 2)}\n`, 'utf-8' ); + await writeFile( + join(root, 'indexes', 'latest-transcript-event.json'), + `${JSON.stringify( + { + eventId: 'evt_capture_123', + eventName: 'capture.transcript.created', + eventVersion: 1, + 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', actorId: 'user_saravana' }, + trace: { + correlationId: 'corr_123', + causationId: null, + parentEventId: null, + }, + payload: { + artifactId: 'art_transcript_123', + durationMs: 1000, + language: 'en', + transcriptSource: 'microphone', + }, + }, + null, + 2 + )}\n`, + 'utf-8' + ); const loaded = await loadLatestTranscriptArtifact(root); + const transcriptCaptureEvent = await loadLatestTranscriptCaptureEvent(root); const generated = buildPhase1NoteImport({ transcriptArtifact: loaded, + transcriptCaptureEvent, workspaceId: 'ws_phase1', userId: 'user_saravana', now: '2026-04-03T18:17:00.000Z', @@ -88,6 +124,18 @@ describe('ecosystem phase1 note import', () => { expect(savedNoteArtifact.payload.noteFormat).toBe('markdown'); expect(savedLinkedEvent.payload.targetArtifactId).toBe('art_transcript_123'); expect(savedLinkedEvent.payload.relation).toBe('summarizes'); + expect(generated.createdEvent.trace.causationId).toBe('evt_capture_123'); + expect(generated.createdEvent.trace.parentEventId).toBe('evt_capture_123'); + expect(savedLinkedEvent.trace.causationId).toBe(generated.createdEvent.eventId); + + const savedCreatedEventIndex = JSON.parse( + await readFile(join(root, 'indexes', 'latest-note-created-event.json'), 'utf-8') + ); + const savedLinkedEventIndex = JSON.parse( + await readFile(join(root, 'indexes', 'latest-note-linked-event.json'), 'utf-8') + ); + expect(savedCreatedEventIndex.eventId).toBe(generated.createdEvent.eventId); + expect(savedLinkedEventIndex.eventId).toBe(generated.linkedEvent.eventId); await rm(root, { recursive: true, force: true }); }); diff --git a/backend/src/lib/ecosystem-phase1.ts b/backend/src/lib/ecosystem-phase1.ts index 6f26b9a..46d04fd 100644 --- a/backend/src/lib/ecosystem-phase1.ts +++ b/backend/src/lib/ecosystem-phase1.ts @@ -64,6 +64,11 @@ type EcosystemEvent = { payload: Record; }; +type TranscriptCaptureEvent = EcosystemEvent & { + eventName: 'capture.transcript.created'; + artifactId: string; +}; + type NoteArtifactEnvelope = { id: string; artifactType: 'note'; @@ -108,8 +113,23 @@ export async function loadLatestTranscriptArtifact(root = getPhase1Root()): Prom return JSON.parse(raw) as TranscriptArtifact; } +export async function loadLatestTranscriptCaptureEvent( + root = getPhase1Root() +): Promise { + try { + const raw = await readFile(join(root, 'indexes', 'latest-transcript-event.json'), 'utf-8'); + return JSON.parse(raw) as TranscriptCaptureEvent; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return null; + } + throw error; + } +} + export function buildPhase1NoteImport(params: { transcriptArtifact: TranscriptArtifact; + transcriptCaptureEvent?: TranscriptCaptureEvent | null; workspaceId: string; userId: string; now?: string; @@ -233,8 +253,8 @@ export function buildPhase1NoteImport(params: { }, trace: { correlationId, - causationId: null, - parentEventId: null, + causationId: params.transcriptCaptureEvent?.eventId ?? null, + parentEventId: params.transcriptCaptureEvent?.eventId ?? null, }, payload: { artifactType: 'note', @@ -285,6 +305,8 @@ export async function persistPhase1NoteOutputs(params: { await writeJson(join(root, 'events', 'artifact.created', `${params.createdEvent.eventId}.json`), params.createdEvent); await writeJson(join(root, 'events', 'artifact.linked', `${params.linkedEvent.eventId}.json`), params.linkedEvent); await writeJson(join(root, 'indexes', 'latest-note.json'), params.ecosystemNoteArtifact); + await writeJson(join(root, 'indexes', 'latest-note-created-event.json'), params.createdEvent); + await writeJson(join(root, 'indexes', 'latest-note-linked-event.json'), params.linkedEvent); } async function writeJson(path: string, payload: unknown): Promise { diff --git a/backend/src/modules/ecosystem-phase1/routes.ts b/backend/src/modules/ecosystem-phase1/routes.ts index 05f030c..55daf93 100644 --- a/backend/src/modules/ecosystem-phase1/routes.ts +++ b/backend/src/modules/ecosystem-phase1/routes.ts @@ -7,6 +7,7 @@ import { createNoteArtifact } from '../note-artifacts/repository.js'; import { buildPhase1NoteImport, loadLatestTranscriptArtifact, + loadLatestTranscriptCaptureEvent, persistPhase1NoteOutputs, } from '../../lib/ecosystem-phase1.js'; @@ -23,8 +24,10 @@ export async function ecosystemPhase1Routes(app: FastifyInstance) { } const transcriptArtifact = await loadLatestTranscriptArtifact(); + const transcriptCaptureEvent = await loadLatestTranscriptCaptureEvent(); const generated = buildPhase1NoteImport({ transcriptArtifact, + transcriptCaptureEvent, workspaceId: parsed.data.workspaceId, userId: auth.sub, });