diff --git a/packages/events/fixtures/ecosystem/phase1/artifact-created.event.json b/packages/events/fixtures/ecosystem/phase1/artifact-created.event.json new file mode 100644 index 00000000..97a233a1 --- /dev/null +++ b/packages/events/fixtures/ecosystem/phase1/artifact-created.event.json @@ -0,0 +1,27 @@ +{ + "eventId": "evt_phase1_002", + "eventName": "artifact.created", + "eventVersion": 1, + "occurredAt": "2026-04-03T18:17:01.000Z", + "productId": "notelett", + "sourceSurface": "web", + "userId": "user_saravana", + "orgId": null, + "sessionId": "sess_lysnr_001", + "runId": "run_notes_001", + "artifactId": "art_note_001", + "actor": { + "actorType": "agent", + "actorId": "notes_ingest_agent" + }, + "trace": { + "correlationId": "corr_phase1_001", + "causationId": "evt_phase1_001", + "parentEventId": "evt_phase1_001" + }, + "payload": { + "artifactType": "note", + "title": "Standup follow-up", + "status": "draft" + } +} diff --git a/packages/events/fixtures/ecosystem/phase1/artifact-linked.event.json b/packages/events/fixtures/ecosystem/phase1/artifact-linked.event.json new file mode 100644 index 00000000..14afb36f --- /dev/null +++ b/packages/events/fixtures/ecosystem/phase1/artifact-linked.event.json @@ -0,0 +1,27 @@ +{ + "eventId": "evt_phase1_003", + "eventName": "artifact.linked", + "eventVersion": 1, + "occurredAt": "2026-04-03T18:17:02.000Z", + "productId": "notelett", + "sourceSurface": "web", + "userId": "user_saravana", + "orgId": null, + "sessionId": "sess_lysnr_001", + "runId": "run_notes_001", + "artifactId": "art_note_001", + "actor": { + "actorType": "agent", + "actorId": "notes_ingest_agent" + }, + "trace": { + "correlationId": "corr_phase1_001", + "causationId": "evt_phase1_002", + "parentEventId": "evt_phase1_002" + }, + "payload": { + "sourceArtifactId": "art_note_001", + "targetArtifactId": "art_transcript_001", + "relation": "summarizes" + } +} diff --git a/packages/events/fixtures/ecosystem/phase1/capture-transcript-created.event.json b/packages/events/fixtures/ecosystem/phase1/capture-transcript-created.event.json new file mode 100644 index 00000000..a1bd3f20 --- /dev/null +++ b/packages/events/fixtures/ecosystem/phase1/capture-transcript-created.event.json @@ -0,0 +1,28 @@ +{ + "eventId": "evt_phase1_001", + "eventName": "capture.transcript.created", + "eventVersion": 1, + "occurredAt": "2026-04-03T18:15:01.000Z", + "productId": "lysnrai", + "sourceSurface": "mobile", + "userId": "user_saravana", + "orgId": null, + "sessionId": "sess_lysnr_001", + "runId": null, + "artifactId": "art_transcript_001", + "actor": { + "actorType": "user", + "actorId": "user_saravana" + }, + "trace": { + "correlationId": "corr_phase1_001", + "causationId": null, + "parentEventId": null + }, + "payload": { + "artifactId": "art_transcript_001", + "durationMs": 42150, + "language": "en", + "transcriptSource": "microphone" + } +} diff --git a/packages/events/fixtures/ecosystem/phase1/memory-artifact.json b/packages/events/fixtures/ecosystem/phase1/memory-artifact.json new file mode 100644 index 00000000..44d219d2 --- /dev/null +++ b/packages/events/fixtures/ecosystem/phase1/memory-artifact.json @@ -0,0 +1,69 @@ +{ + "id": "art_memory_001", + "artifactType": "memory", + "schemaVersion": 1, + "productId": "mindlyst", + "sourceSurface": "service", + "title": "Saravana prefers deployment checklist reminders after standup", + "summary": "Memory candidate inferred from the standup transcript and note.", + "createdAt": "2026-04-03T18:20:00.000Z", + "updatedAt": "2026-04-03T18:20:00.000Z", + "createdBy": { + "actorType": "agent", + "actorId": "memory_ingest_agent" + }, + "ownership": { + "userId": "user_saravana", + "orgId": null + }, + "visibility": { + "scope": "private" + }, + "status": "proposed", + "tags": ["memory", "workflow"], + "links": [ + { + "relation": "derived-from", + "targetArtifactId": "art_transcript_001" + }, + { + "relation": "generated-memory", + "targetArtifactId": "art_note_001" + } + ], + "provenance": { + "originProductId": "lysnrai", + "originActionId": "capture_001", + "sessionId": "sess_lysnr_001", + "runId": "run_memory_001", + "approvalId": null, + "correlationId": "corr_phase1_001", + "lineage": [ + { + "stepType": "captured", + "productId": "lysnrai", + "actorType": "user", + "timestamp": "2026-04-03T18:15:00.000Z" + }, + { + "stepType": "note-created", + "productId": "notelett", + "actorType": "agent", + "timestamp": "2026-04-03T18:17:00.000Z" + }, + { + "stepType": "memory-proposed", + "productId": "mindlyst", + "actorType": "agent", + "timestamp": "2026-04-03T18:20:00.000Z" + } + ] + }, + "payload": { + "memoryKind": "preference", + "text": "Saravana benefits from deployment checklist reminders immediately after standup capture.", + "confidence": 0.82, + "sourceArtifactIds": ["art_transcript_001", "art_note_001"], + "reviewState": "proposed" + } +} diff --git a/packages/events/fixtures/ecosystem/phase1/memory-entry-created.event.json b/packages/events/fixtures/ecosystem/phase1/memory-entry-created.event.json new file mode 100644 index 00000000..81bfd92f --- /dev/null +++ b/packages/events/fixtures/ecosystem/phase1/memory-entry-created.event.json @@ -0,0 +1,29 @@ +{ + "eventId": "evt_phase1_004", + "eventName": "memory.entry.created", + "eventVersion": 1, + "occurredAt": "2026-04-03T18:20:01.000Z", + "productId": "mindlyst", + "sourceSurface": "service", + "userId": "user_saravana", + "orgId": null, + "sessionId": "sess_lysnr_001", + "runId": "run_memory_001", + "artifactId": "art_memory_001", + "actor": { + "actorType": "agent", + "actorId": "memory_ingest_agent" + }, + "trace": { + "correlationId": "corr_phase1_001", + "causationId": "evt_phase1_003", + "parentEventId": "evt_phase1_003" + }, + "payload": { + "artifactId": "art_memory_001", + "memoryKind": "preference", + "reviewState": "proposed", + "confidence": 0.82, + "sourceArtifactIds": ["art_transcript_001", "art_note_001"] + } +} diff --git a/packages/events/fixtures/ecosystem/phase1/note-artifact.json b/packages/events/fixtures/ecosystem/phase1/note-artifact.json new file mode 100644 index 00000000..c7858764 --- /dev/null +++ b/packages/events/fixtures/ecosystem/phase1/note-artifact.json @@ -0,0 +1,57 @@ +{ + "id": "art_note_001", + "artifactType": "note", + "schemaVersion": 1, + "productId": "notelett", + "sourceSurface": "web", + "title": "Standup follow-up", + "summary": "Structured note created from a LysnrAI transcript.", + "createdAt": "2026-04-03T18:17:00.000Z", + "updatedAt": "2026-04-03T18:17:00.000Z", + "createdBy": { + "actorType": "agent", + "actorId": "notes_ingest_agent" + }, + "ownership": { + "userId": "user_saravana", + "orgId": null + }, + "visibility": { + "scope": "private" + }, + "status": "draft", + "tags": ["derived", "standup"], + "links": [ + { + "relation": "summarizes", + "targetArtifactId": "art_transcript_001" + } + ], + "provenance": { + "originProductId": "lysnrai", + "originActionId": "capture_001", + "sessionId": "sess_lysnr_001", + "runId": "run_notes_001", + "approvalId": null, + "correlationId": "corr_phase1_001", + "lineage": [ + { + "stepType": "captured", + "productId": "lysnrai", + "actorType": "user", + "timestamp": "2026-04-03T18:15:00.000Z" + }, + { + "stepType": "note-created", + "productId": "notelett", + "actorType": "agent", + "timestamp": "2026-04-03T18:17:00.000Z" + } + ] + }, + "payload": { + "noteFormat": "markdown", + "body": "# Standup follow-up\n\n- Billing sync finished\n- Clean up follow-up notes\n- Review deployment checklist", + "excerpt": "Billing sync finished; follow-up notes and deployment checklist remain." + } +} diff --git a/packages/events/fixtures/ecosystem/phase1/transcript-artifact.json b/packages/events/fixtures/ecosystem/phase1/transcript-artifact.json new file mode 100644 index 00000000..f696a057 --- /dev/null +++ b/packages/events/fixtures/ecosystem/phase1/transcript-artifact.json @@ -0,0 +1,56 @@ +{ + "id": "art_transcript_001", + "artifactType": "transcript", + "schemaVersion": 1, + "productId": "lysnrai", + "sourceSurface": "mobile", + "title": "Daily standup voice capture", + "summary": "Transcript captured from Saravana's daily standup reflection.", + "createdAt": "2026-04-03T18:15:00.000Z", + "updatedAt": "2026-04-03T18:15:00.000Z", + "createdBy": { + "actorType": "user", + "actorId": "user_saravana" + }, + "ownership": { + "userId": "user_saravana", + "orgId": null + }, + "visibility": { + "scope": "private", + "allowedProducts": ["learning_ai_notes", "learning_multimodal_memory_agents"] + }, + "status": "completed", + "tags": ["voice", "standup"], + "links": [], + "provenance": { + "originProductId": "lysnrai", + "originActionId": "capture_001", + "sessionId": "sess_lysnr_001", + "runId": null, + "approvalId": null, + "correlationId": "corr_phase1_001", + "lineage": [ + { + "stepType": "captured", + "productId": "lysnrai", + "actorType": "user", + "timestamp": "2026-04-03T18:15:00.000Z" + } + ] + }, + "payload": { + "transcriptText": "Today I finished the billing sync, I need to clean up follow-up notes, and I should remember to review the deployment checklist.", + "transcriptSource": "microphone", + "language": "en", + "durationMs": 42150, + "segments": [ + { + "speaker": null, + "startedAtMs": 0, + "endedAtMs": 42150, + "text": "Today I finished the billing sync, I need to clean up follow-up notes, and I should remember to review the deployment checklist." + } + ] + } +} diff --git a/packages/events/src/ecosystem.test.ts b/packages/events/src/ecosystem.test.ts new file mode 100644 index 00000000..31ce87cf --- /dev/null +++ b/packages/events/src/ecosystem.test.ts @@ -0,0 +1,59 @@ +import { describe, expect, it } from 'vitest'; +import transcriptArtifact from '../fixtures/ecosystem/phase1/transcript-artifact.json' with { type: 'json' }; +import noteArtifact from '../fixtures/ecosystem/phase1/note-artifact.json' with { type: 'json' }; +import memoryArtifact from '../fixtures/ecosystem/phase1/memory-artifact.json' with { type: 'json' }; +import captureTranscriptCreatedEvent from '../fixtures/ecosystem/phase1/capture-transcript-created.event.json' with { type: 'json' }; +import artifactCreatedEvent from '../fixtures/ecosystem/phase1/artifact-created.event.json' with { type: 'json' }; +import artifactLinkedEvent from '../fixtures/ecosystem/phase1/artifact-linked.event.json' with { type: 'json' }; +import memoryEntryCreatedEvent from '../fixtures/ecosystem/phase1/memory-entry-created.event.json' with { type: 'json' }; +import { + Phase1ArtifactEnvelopeSchema, + Phase1EcosystemEventSchema, + Phase1EcosystemEventSchemas, +} from './ecosystem.js'; + +describe('phase1 ecosystem contracts', () => { + it('validates canonical transcript, note, and memory artifacts', () => { + const transcript = Phase1ArtifactEnvelopeSchema.parse(transcriptArtifact); + const note = Phase1ArtifactEnvelopeSchema.parse(noteArtifact); + const memory = Phase1ArtifactEnvelopeSchema.parse(memoryArtifact); + + expect(transcript.artifactType).toBe('transcript'); + expect(note.links).toContainEqual({ + relation: 'summarizes', + targetArtifactId: transcript.id, + }); + expect(memory.links).toEqual( + expect.arrayContaining([ + { + relation: 'generated-memory', + targetArtifactId: note.id, + }, + ]) + ); + }); + + it('validates canonical phase1 events', () => { + const events = [ + captureTranscriptCreatedEvent, + artifactCreatedEvent, + artifactLinkedEvent, + memoryEntryCreatedEvent, + ].map(event => Phase1EcosystemEventSchema.parse(event)); + + expect(events.map(event => event.eventName)).toEqual([ + 'capture.transcript.created', + 'artifact.created', + 'artifact.linked', + 'memory.entry.created', + ]); + }); + + it('exposes event-specific schemas keyed by canonical event name', () => { + const created = Phase1EcosystemEventSchemas['artifact.created'].parse(artifactCreatedEvent); + const linked = Phase1EcosystemEventSchemas['artifact.linked'].parse(artifactLinkedEvent); + + expect(created.payload.artifactType).toBe('note'); + expect(linked.payload.relation).toBe('summarizes'); + }); +}); diff --git a/packages/events/src/ecosystem.ts b/packages/events/src/ecosystem.ts new file mode 100644 index 00000000..8ff5e44c --- /dev/null +++ b/packages/events/src/ecosystem.ts @@ -0,0 +1,232 @@ +import { z } from 'zod'; + +export const EcosystemArtifactTypeSchema = z.enum([ + 'transcript', + 'note', + 'memory', + 'plan', + 'routine', + 'habit-checkin', + 'trail-report', + 'route-session', + 'agent-output', + 'document', + 'digest', +]); + +export const ArtifactLinkRelationSchema = z.enum([ + 'derived-from', + 'summarizes', + 'generated-task', + 'generated-routine', + 'generated-memory', + 'evidence-for', + 'review-of', + 'attached-to', +]); + +export const ArtifactLinkSchema = z.object({ + relation: ArtifactLinkRelationSchema, + targetArtifactId: z.string().min(1), +}); + +export const ArtifactCreatedBySchema = z.object({ + actorType: z.enum(['user', 'agent', 'system', 'mixed']), + actorId: z.string().min(1).nullable(), +}); + +export const ArtifactOwnershipSchema = z.object({ + userId: z.string().min(1), + orgId: z.string().min(1).nullable().optional(), +}); + +export const ArtifactVisibilitySchema = z.object({ + scope: z.enum(['private', 'org', 'shared', 'local-only']), + allowedProducts: z.array(z.string().min(1)).optional(), +}); + +export const ArtifactLineageStepSchema = z.object({ + stepType: z.string().min(1), + productId: z.string().min(1), + actorType: z.enum(['user', 'agent', 'system']), + timestamp: z.string().datetime(), +}); + +export const ArtifactProvenanceSchema = z.object({ + originProductId: z.string().min(1), + originActionId: z.string().min(1).nullable().optional(), + sessionId: z.string().min(1).nullable().optional(), + runId: z.string().min(1).nullable().optional(), + approvalId: z.string().min(1).nullable().optional(), + correlationId: z.string().min(1).nullable().optional(), + lineage: z.array(ArtifactLineageStepSchema).min(1), +}); + +export const BaseArtifactEnvelopeSchema = z.object({ + id: z.string().min(1), + artifactType: EcosystemArtifactTypeSchema, + schemaVersion: z.literal(1), + productId: z.string().min(1), + sourceSurface: z.string().min(1), + title: z.string().min(1).nullable(), + summary: z.string().min(1).nullable(), + createdAt: z.string().datetime(), + updatedAt: z.string().datetime(), + createdBy: ArtifactCreatedBySchema, + ownership: ArtifactOwnershipSchema, + visibility: ArtifactVisibilitySchema, + status: z.string().min(1), + tags: z.array(z.string().min(1)), + links: z.array(ArtifactLinkSchema), + provenance: ArtifactProvenanceSchema, + payload: z.record(z.unknown()), +}); + +export const TranscriptPayloadSchema = z.object({ + transcriptText: z.string().min(1), + transcriptSource: z.enum(['microphone', 'upload', 'call', 'browser', 'other']), + language: z.string().min(1), + durationMs: z.number().int().nonnegative(), + segments: z + .array( + z.object({ + speaker: z.string().min(1).nullable().optional(), + startedAtMs: z.number().int().nonnegative(), + endedAtMs: z.number().int().nonnegative(), + text: z.string().min(1), + }) + ) + .default([]), +}); + +export const NotePayloadSchema = z.object({ + noteFormat: z.enum(['markdown', 'plain-text', 'rich-text']), + body: z.string().min(1), + excerpt: z.string().min(1).nullable().optional(), +}); + +export const MemoryPayloadSchema = z.object({ + memoryKind: z.enum(['fact', 'preference', 'person', 'project', 'insight', 'todo']), + text: z.string().min(1), + confidence: z.number().min(0).max(1), + sourceArtifactIds: z.array(z.string().min(1)).min(1), + reviewState: z.enum(['proposed', 'accepted', 'rejected']), +}); + +export const TranscriptArtifactEnvelopeSchema = BaseArtifactEnvelopeSchema.extend({ + artifactType: z.literal('transcript'), + payload: TranscriptPayloadSchema, +}); + +export const NoteArtifactEnvelopeSchema = BaseArtifactEnvelopeSchema.extend({ + artifactType: z.literal('note'), + payload: NotePayloadSchema, +}); + +export const MemoryArtifactEnvelopeSchema = BaseArtifactEnvelopeSchema.extend({ + artifactType: z.literal('memory'), + payload: MemoryPayloadSchema, +}); + +export const Phase1ArtifactEnvelopeSchema = z.discriminatedUnion('artifactType', [ + TranscriptArtifactEnvelopeSchema, + NoteArtifactEnvelopeSchema, + MemoryArtifactEnvelopeSchema, +]); + +export const EcosystemEventActorSchema = z.object({ + actorType: z.enum(['user', 'agent', 'system', 'device']), + actorId: z.string().min(1).nullable().optional(), +}); + +export const EcosystemEventTraceSchema = z.object({ + correlationId: z.string().min(1).nullable(), + causationId: z.string().min(1).nullable(), + parentEventId: z.string().min(1).nullable(), +}); + +export const BaseEcosystemEventSchema = z.object({ + eventId: z.string().min(1), + eventName: z.string().min(1), + eventVersion: z.literal(1), + occurredAt: z.string().datetime(), + productId: z.string().min(1), + sourceSurface: z.string().min(1), + userId: z.string().min(1).nullable().optional(), + orgId: z.string().min(1).nullable().optional(), + sessionId: z.string().min(1).nullable().optional(), + runId: z.string().min(1).nullable().optional(), + artifactId: z.string().min(1).nullable().optional(), + actor: EcosystemEventActorSchema, + trace: EcosystemEventTraceSchema, + payload: z.record(z.unknown()), +}); + +export const CaptureTranscriptCreatedPayloadSchema = z.object({ + artifactId: z.string().min(1), + durationMs: z.number().int().nonnegative(), + language: z.string().min(1), + transcriptSource: z.enum(['microphone', 'upload', 'call', 'browser', 'other']), +}); + +export const ArtifactCreatedPayloadSchema = z.object({ + artifactType: z.enum(['transcript', 'note', 'memory']), + title: z.string().min(1).nullable(), + status: z.string().min(1), +}); + +export const ArtifactLinkedPayloadSchema = z.object({ + sourceArtifactId: z.string().min(1), + targetArtifactId: z.string().min(1), + relation: z.enum(['summarizes', 'generated-memory']), +}); + +export const MemoryEntryCreatedPayloadSchema = z.object({ + artifactId: z.string().min(1), + memoryKind: MemoryPayloadSchema.shape.memoryKind, + reviewState: MemoryPayloadSchema.shape.reviewState, + confidence: MemoryPayloadSchema.shape.confidence, + sourceArtifactIds: z.array(z.string().min(1)).min(1), +}); + +export const CaptureTranscriptCreatedEventSchema = BaseEcosystemEventSchema.extend({ + eventName: z.literal('capture.transcript.created'), + payload: CaptureTranscriptCreatedPayloadSchema, +}); + +export const ArtifactCreatedEventSchema = BaseEcosystemEventSchema.extend({ + eventName: z.literal('artifact.created'), + payload: ArtifactCreatedPayloadSchema, +}); + +export const ArtifactLinkedEventSchema = BaseEcosystemEventSchema.extend({ + eventName: z.literal('artifact.linked'), + payload: ArtifactLinkedPayloadSchema, +}); + +export const MemoryEntryCreatedEventSchema = BaseEcosystemEventSchema.extend({ + eventName: z.literal('memory.entry.created'), + payload: MemoryEntryCreatedPayloadSchema, +}); + +export const Phase1EcosystemEventSchema = z.discriminatedUnion('eventName', [ + CaptureTranscriptCreatedEventSchema, + ArtifactCreatedEventSchema, + ArtifactLinkedEventSchema, + MemoryEntryCreatedEventSchema, +]); + +export const Phase1EcosystemEventSchemas = { + 'capture.transcript.created': CaptureTranscriptCreatedEventSchema, + 'artifact.created': ArtifactCreatedEventSchema, + 'artifact.linked': ArtifactLinkedEventSchema, + 'memory.entry.created': MemoryEntryCreatedEventSchema, +} as const; + +export type ArtifactEnvelope = z.infer; +export type TranscriptArtifactEnvelope = z.infer; +export type NoteArtifactEnvelope = z.infer; +export type MemoryArtifactEnvelope = z.infer; +export type Phase1ArtifactEnvelope = z.infer; +export type EcosystemEvent = z.infer; +export type Phase1EcosystemEvent = z.infer; diff --git a/packages/events/src/index.ts b/packages/events/src/index.ts index 2e0d02a9..636009dc 100644 --- a/packages/events/src/index.ts +++ b/packages/events/src/index.ts @@ -3,6 +3,37 @@ export type { EmitResult, EmitError } from './memory.js'; export { DurableEventBus } from './durable.js'; export type { DurableEventBusOptions } from './durable.js'; export { PlatformEventSchemas } from './types.js'; +export { + ArtifactCreatedBySchema, + ArtifactLineageStepSchema, + ArtifactLinkRelationSchema, + ArtifactLinkSchema, + ArtifactOwnershipSchema, + ArtifactProvenanceSchema, + ArtifactVisibilitySchema, + BaseArtifactEnvelopeSchema, + BaseEcosystemEventSchema, + CaptureTranscriptCreatedEventSchema, + CaptureTranscriptCreatedPayloadSchema, + EcosystemArtifactTypeSchema, + EcosystemEventActorSchema, + EcosystemEventTraceSchema, + MemoryArtifactEnvelopeSchema, + MemoryEntryCreatedEventSchema, + MemoryEntryCreatedPayloadSchema, + MemoryPayloadSchema, + NoteArtifactEnvelopeSchema, + NotePayloadSchema, + Phase1ArtifactEnvelopeSchema, + Phase1EcosystemEventSchema, + Phase1EcosystemEventSchemas, + TranscriptArtifactEnvelopeSchema, + TranscriptPayloadSchema, + ArtifactCreatedEventSchema, + ArtifactCreatedPayloadSchema, + ArtifactLinkedEventSchema, + ArtifactLinkedPayloadSchema, +} from './ecosystem.js'; export type { PlatformEventName, PlatformEventPayload, @@ -10,3 +41,11 @@ export type { EventHandler, EventSubscription, } from './types.js'; +export type { + EcosystemEvent, + MemoryArtifactEnvelope, + NoteArtifactEnvelope, + Phase1ArtifactEnvelope, + Phase1EcosystemEvent, + TranscriptArtifactEnvelope, +} from './ecosystem.js';