diff --git a/packages/events/src/ecosystem.test.ts b/packages/events/src/ecosystem.test.ts index c1de536e..6f83cc01 100644 --- a/packages/events/src/ecosystem.test.ts +++ b/packages/events/src/ecosystem.test.ts @@ -15,6 +15,7 @@ import { Phase1EcosystemEventSchema, Phase1EcosystemEventSchemas, RoutineArtifactEnvelopeSchema, + TrailReportArtifactEnvelopeSchema, } from './ecosystem.js'; describe('phase1 ecosystem contracts', () => { @@ -290,3 +291,111 @@ describe('phase2 ecosystem contract extensions', () => { expect(linked.payload.relation).toBe('generated-habit'); }); }); + +describe('phase3 ecosystem contract extensions', () => { + it('validates canonical trail-report artifacts', () => { + const trailReport = TrailReportArtifactEnvelopeSchema.parse({ + id: 'art_trail_demo', + artifactType: 'trail-report', + schemaVersion: 1, + productId: 'actiontrail', + sourceSurface: 'backend', + title: 'Cowork audit report for task task-123', + summary: '4 audited actions with 1 safety signal', + createdAt: '2026-04-03T14:00:00.000Z', + updatedAt: '2026-04-03T14:00:00.000Z', + createdBy: { actorType: 'agent', actorId: 'phase3-audit-importer' }, + ownership: { userId: 'saravana', orgId: null }, + visibility: { + scope: 'private', + allowedProducts: ['learning_ai_notes', 'learning_multimodal_memory_agents'], + }, + status: 'recorded', + tags: ['ecosystem', 'phase3', 'audit'], + links: [], + provenance: { + originProductId: 'claw-cowork', + originActionId: 'task-123', + sessionId: 'sess_phase3', + runId: 'run_phase3_trail', + approvalId: null, + correlationId: 'corr_phase3', + lineage: [ + { + stepType: 'audit-exported', + productId: 'claw-cowork', + actorType: 'system', + timestamp: '2026-04-03T13:55:00.000Z', + }, + { + stepType: 'trail-report-created', + productId: 'actiontrail', + actorType: 'agent', + timestamp: '2026-04-03T14:00:00.000Z', + }, + ], + }, + payload: { + sourceProduct: 'claw-cowork', + sourceTaskId: 'task-123', + generatedFrom: 'audit-export-json', + reportGeneratedAt: '2026-04-03T14:00:00.000Z', + actionCount: 4, + toolCallCount: 2, + approvalCount: 1, + failureCount: 0, + safetySignalCount: 1, + tasks: ['task-123'], + actionBreakdown: [ + { action: 'TaskStarted', count: 1 }, + { action: 'ToolCall', count: 2 }, + { action: 'InjectionDetected', count: 1 }, + ], + entries: [ + { + timestamp: '2026-04-03T13:55:00.000Z', + taskId: 'task-123', + action: 'TaskStarted', + tool: null, + result: 'Success', + approval: null, + inputSummary: 'Audit seed task', + metadata: { surface: 'desktop' }, + }, + ], + }, + }); + + expect(trailReport.payload.sourceProduct).toBe('claw-cowork'); + expect(trailReport.payload.safetySignalCount).toBe(1); + }); + + it('accepts generic artifact.created events for trail-report artifacts', () => { + const created = ArtifactCreatedEventSchema.parse({ + eventId: 'evt_phase3_created', + eventName: 'artifact.created', + eventVersion: 1, + occurredAt: '2026-04-03T14:00:00.000Z', + productId: 'actiontrail', + sourceSurface: 'backend', + userId: 'saravana', + orgId: null, + sessionId: 'sess_phase3', + runId: 'run_phase3_trail', + artifactId: 'art_trail_demo', + actor: { actorType: 'agent', actorId: 'phase3-audit-importer' }, + trace: { + correlationId: 'corr_phase3', + causationId: 'evt_cowork_export', + parentEventId: 'evt_cowork_export', + }, + payload: { + artifactType: 'trail-report', + title: 'Cowork audit report for task task-123', + status: 'recorded', + }, + }); + + expect(created.payload.artifactType).toBe('trail-report'); + }); +}); diff --git a/packages/events/src/ecosystem.ts b/packages/events/src/ecosystem.ts index f72435fe..178ec879 100644 --- a/packages/events/src/ecosystem.ts +++ b/packages/events/src/ecosystem.ts @@ -162,6 +162,37 @@ export const HabitPayloadSchema = z.object({ sourceRoutineId: z.string().min(1), }); +export const TrailReportPayloadSchema = z.object({ + sourceProduct: z.literal('claw-cowork'), + sourceTaskId: z.string().min(1).nullable().optional(), + generatedFrom: z.enum(['audit-export-json', 'audit-query-json']), + reportGeneratedAt: z.string().datetime(), + actionCount: z.number().int().nonnegative(), + toolCallCount: z.number().int().nonnegative(), + approvalCount: z.number().int().nonnegative(), + failureCount: z.number().int().nonnegative(), + safetySignalCount: z.number().int().nonnegative(), + tasks: z.array(z.string().min(1)), + actionBreakdown: z.array( + z.object({ + action: z.string().min(1), + count: z.number().int().positive(), + }) + ), + entries: z.array( + z.object({ + timestamp: z.string().datetime(), + taskId: z.string().min(1).nullable(), + action: z.string().min(1), + tool: z.string().min(1).nullable().optional(), + result: z.string().min(1).nullable().optional(), + approval: z.string().min(1).nullable().optional(), + inputSummary: z.string().min(1).nullable().optional(), + metadata: z.record(z.unknown()).nullable().optional(), + }) + ), +}); + export const TranscriptArtifactEnvelopeSchema = BaseArtifactEnvelopeSchema.extend({ artifactType: z.literal('transcript'), payload: TranscriptPayloadSchema, @@ -192,6 +223,11 @@ export const HabitArtifactEnvelopeSchema = BaseArtifactEnvelopeSchema.extend({ payload: HabitPayloadSchema, }); +export const TrailReportArtifactEnvelopeSchema = BaseArtifactEnvelopeSchema.extend({ + artifactType: z.literal('trail-report'), + payload: TrailReportPayloadSchema, +}); + export const Phase1ArtifactEnvelopeSchema = z.discriminatedUnion('artifactType', [ TranscriptArtifactEnvelopeSchema, NoteArtifactEnvelopeSchema, @@ -234,7 +270,15 @@ export const CaptureTranscriptCreatedPayloadSchema = z.object({ }); export const ArtifactCreatedPayloadSchema = z.object({ - artifactType: z.enum(['transcript', 'note', 'memory', 'plan', 'routine', 'habit']), + artifactType: z.enum([ + 'transcript', + 'note', + 'memory', + 'plan', + 'routine', + 'habit', + 'trail-report', + ]), title: z.string().min(1).nullable(), status: z.string().min(1), });