feat(phase3): add notelett trail report import
This commit is contained in:
parent
623d02c32f
commit
4af86b43f7
90
backend/src/lib/ecosystem-phase3.test.ts
Normal file
90
backend/src/lib/ecosystem-phase3.test.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import {
|
||||||
|
buildPhase3TrailNoteImport,
|
||||||
|
loadLatestTrailReportArtifact,
|
||||||
|
loadLatestTrailReportCreatedEvent,
|
||||||
|
persistPhase3NoteOutputs,
|
||||||
|
} from './ecosystem-phase3.ts';
|
||||||
|
|
||||||
|
test('builds and persists a NoteLett audit note from the latest trail report artifact', async () => {
|
||||||
|
const root = await mkdtemp(join(tmpdir(), 'notelett-phase3-'));
|
||||||
|
await mkdir(join(root, 'indexes'), { recursive: true });
|
||||||
|
await writeFile(
|
||||||
|
join(root, 'indexes', 'latest-trail-report.json'),
|
||||||
|
`${JSON.stringify({
|
||||||
|
id: 'art_trail_demo',
|
||||||
|
title: 'Cowork audit report for task task_phase3_demo',
|
||||||
|
summary: '3 audited actions, 1 safety signal',
|
||||||
|
ownership: { userId: 'saravana', orgId: null },
|
||||||
|
provenance: {
|
||||||
|
sessionId: 'sess_phase3',
|
||||||
|
correlationId: 'corr_phase3',
|
||||||
|
lineage: [
|
||||||
|
{
|
||||||
|
stepType: 'trail-report-created',
|
||||||
|
productId: 'actiontrail',
|
||||||
|
actorType: 'agent',
|
||||||
|
timestamp: '2026-04-03T14:05:00.000Z',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
sourceProduct: 'claw-cowork',
|
||||||
|
sourceTaskId: 'task_phase3_demo',
|
||||||
|
reportGeneratedAt: '2026-04-03T14:05:00.000Z',
|
||||||
|
actionCount: 3,
|
||||||
|
toolCallCount: 1,
|
||||||
|
approvalCount: 1,
|
||||||
|
failureCount: 0,
|
||||||
|
safetySignalCount: 1,
|
||||||
|
actionBreakdown: [
|
||||||
|
{ action: 'ToolCall', count: 1 },
|
||||||
|
{ action: 'InjectionDetected', count: 1 },
|
||||||
|
],
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
timestamp: '2026-04-03T14:01:00.000Z',
|
||||||
|
taskId: 'task_phase3_demo',
|
||||||
|
action: 'ToolCall',
|
||||||
|
tool: 'search_query',
|
||||||
|
inputSummary: 'Find roadmap delta',
|
||||||
|
result: 'Success',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}, null, 2)}\n`,
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
await writeFile(
|
||||||
|
join(root, 'indexes', 'latest-trail-report-created-event.json'),
|
||||||
|
`${JSON.stringify({ eventId: 'evt_trail_created_demo', eventName: 'artifact.created', trace: { correlationId: 'corr_phase3', causationId: 'task_phase3_demo', parentEventId: 'task_phase3_demo' } }, null, 2)}\n`,
|
||||||
|
'utf-8'
|
||||||
|
);
|
||||||
|
|
||||||
|
const trailReportArtifact = await loadLatestTrailReportArtifact(root);
|
||||||
|
const trailReportCreatedEvent = await loadLatestTrailReportCreatedEvent(root);
|
||||||
|
const generated = buildPhase3TrailNoteImport({
|
||||||
|
trailReportArtifact,
|
||||||
|
trailReportCreatedEvent,
|
||||||
|
workspaceId: 'ws_phase3',
|
||||||
|
userId: 'saravana',
|
||||||
|
root,
|
||||||
|
now: '2026-04-03T14:10:00.000Z',
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(generated.note.sourceType, 'ecosystem-trail-report');
|
||||||
|
assert.equal(generated.createdEvent.trace.causationId, 'evt_trail_created_demo');
|
||||||
|
assert.equal(generated.linkedEvent.payload.relation, 'summarizes');
|
||||||
|
|
||||||
|
await persistPhase3NoteOutputs({ ...generated, root });
|
||||||
|
|
||||||
|
const linkedEvent = JSON.parse(
|
||||||
|
await readFile(join(root, 'indexes', 'latest-note-linked-event.json'), 'utf-8')
|
||||||
|
) as { payload: { targetArtifactId: string } };
|
||||||
|
|
||||||
|
assert.equal(linkedEvent.payload.targetArtifactId, 'art_trail_demo');
|
||||||
|
});
|
||||||
353
backend/src/lib/ecosystem-phase3.ts
Normal file
353
backend/src/lib/ecosystem-phase3.ts
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
import { randomUUID } from 'node:crypto';
|
||||||
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
||||||
|
import { homedir } from 'node:os';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
import type { NoteArtifactDoc } from '../modules/note-artifacts/types.js';
|
||||||
|
import type { NoteDoc } from '../modules/notes/types.js';
|
||||||
|
|
||||||
|
const DEFAULT_PHASE3_ROOT = join(homedir(), '.bytelyst', 'ecosystem', 'phase3');
|
||||||
|
|
||||||
|
type TrailReportArtifact = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
summary: string;
|
||||||
|
ownership: {
|
||||||
|
userId: string;
|
||||||
|
orgId?: string | null;
|
||||||
|
};
|
||||||
|
provenance: {
|
||||||
|
sessionId?: string | null;
|
||||||
|
correlationId?: string | null;
|
||||||
|
lineage: Array<{
|
||||||
|
stepType: string;
|
||||||
|
productId: string;
|
||||||
|
actorType: 'user' | 'agent' | 'system';
|
||||||
|
timestamp: string;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
payload: {
|
||||||
|
sourceProduct: 'claw-cowork';
|
||||||
|
sourceTaskId?: string | null;
|
||||||
|
reportGeneratedAt: string;
|
||||||
|
actionCount: number;
|
||||||
|
toolCallCount: number;
|
||||||
|
approvalCount: number;
|
||||||
|
failureCount: number;
|
||||||
|
safetySignalCount: number;
|
||||||
|
actionBreakdown: Array<{
|
||||||
|
action: string;
|
||||||
|
count: number;
|
||||||
|
}>;
|
||||||
|
entries: Array<{
|
||||||
|
timestamp: string;
|
||||||
|
taskId: string | null;
|
||||||
|
action: string;
|
||||||
|
tool?: string | null;
|
||||||
|
result?: string | null;
|
||||||
|
approval?: string | null;
|
||||||
|
inputSummary?: string | null;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type TrailReportCreatedEvent = {
|
||||||
|
eventId: string;
|
||||||
|
eventName: 'artifact.created';
|
||||||
|
trace: {
|
||||||
|
correlationId: string | null;
|
||||||
|
causationId: string | null;
|
||||||
|
parentEventId: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type NoteArtifactEnvelope = {
|
||||||
|
id: string;
|
||||||
|
artifactType: 'note';
|
||||||
|
schemaVersion: 1;
|
||||||
|
productId: 'notelett';
|
||||||
|
sourceSurface: 'backend';
|
||||||
|
title: string;
|
||||||
|
summary: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
createdBy: {
|
||||||
|
actorType: 'agent';
|
||||||
|
actorId: string;
|
||||||
|
};
|
||||||
|
ownership: {
|
||||||
|
userId: string;
|
||||||
|
orgId?: string | null;
|
||||||
|
};
|
||||||
|
visibility: {
|
||||||
|
scope: 'private';
|
||||||
|
allowedProducts: ['learning_multimodal_memory_agents'];
|
||||||
|
};
|
||||||
|
status: 'draft';
|
||||||
|
tags: string[];
|
||||||
|
links: Array<{
|
||||||
|
relation: 'summarizes';
|
||||||
|
targetArtifactId: string;
|
||||||
|
}>;
|
||||||
|
provenance: TrailReportArtifact['provenance'] & {
|
||||||
|
runId: string;
|
||||||
|
};
|
||||||
|
payload: {
|
||||||
|
noteFormat: 'markdown';
|
||||||
|
body: string;
|
||||||
|
excerpt: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type EcosystemEvent = {
|
||||||
|
eventId: string;
|
||||||
|
eventName: 'artifact.created' | 'artifact.linked';
|
||||||
|
eventVersion: 1;
|
||||||
|
occurredAt: string;
|
||||||
|
productId: 'notelett';
|
||||||
|
sourceSurface: 'backend';
|
||||||
|
userId: string;
|
||||||
|
orgId?: string | null;
|
||||||
|
sessionId?: string | null;
|
||||||
|
runId: string;
|
||||||
|
artifactId: string;
|
||||||
|
actor: {
|
||||||
|
actorType: 'agent';
|
||||||
|
actorId: string;
|
||||||
|
};
|
||||||
|
trace: {
|
||||||
|
correlationId: string | null;
|
||||||
|
causationId: string | null;
|
||||||
|
parentEventId: string | null;
|
||||||
|
};
|
||||||
|
payload: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getPhase3Root(): string {
|
||||||
|
return process.env.BYTELYST_ECOSYSTEM_DIR ?? DEFAULT_PHASE3_ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadLatestTrailReportArtifact(root = getPhase3Root()): Promise<TrailReportArtifact> {
|
||||||
|
const raw = await readFile(join(root, 'indexes', 'latest-trail-report.json'), 'utf-8');
|
||||||
|
return JSON.parse(raw) as TrailReportArtifact;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadLatestTrailReportCreatedEvent(
|
||||||
|
root = getPhase3Root()
|
||||||
|
): Promise<TrailReportCreatedEvent | null> {
|
||||||
|
try {
|
||||||
|
const raw = await readFile(join(root, 'indexes', 'latest-trail-report-created-event.json'), 'utf-8');
|
||||||
|
return JSON.parse(raw) as TrailReportCreatedEvent;
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildPhase3TrailNoteImport(params: {
|
||||||
|
trailReportArtifact: TrailReportArtifact;
|
||||||
|
trailReportCreatedEvent?: TrailReportCreatedEvent | null;
|
||||||
|
workspaceId: string;
|
||||||
|
userId: string;
|
||||||
|
root?: string;
|
||||||
|
now?: string;
|
||||||
|
}) {
|
||||||
|
const now = params.now ?? new Date().toISOString();
|
||||||
|
const noteId = `note_phase3_${randomUUID().replace(/-/g, '').slice(0, 12)}`;
|
||||||
|
const noteArtifactId = `art_note_${randomUUID().replace(/-/g, '').slice(0, 12)}`;
|
||||||
|
const runId = `run_note_${randomUUID().replace(/-/g, '').slice(0, 10)}`;
|
||||||
|
const title = params.trailReportArtifact.title || 'Imported audit note';
|
||||||
|
const breakdown = params.trailReportArtifact.payload.actionBreakdown
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(item => `- ${item.action}: ${item.count}`)
|
||||||
|
.join('\n');
|
||||||
|
const evidence = params.trailReportArtifact.payload.entries
|
||||||
|
.slice(0, 3)
|
||||||
|
.map(
|
||||||
|
entry =>
|
||||||
|
`- ${entry.timestamp} | ${entry.action}${entry.tool ? ` (${entry.tool})` : ''}${entry.inputSummary ? ` — ${entry.inputSummary}` : ''}`
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
const body = [
|
||||||
|
`# ${title}`,
|
||||||
|
'',
|
||||||
|
params.trailReportArtifact.summary,
|
||||||
|
'',
|
||||||
|
`Source product: ${params.trailReportArtifact.payload.sourceProduct}`,
|
||||||
|
`Source task: ${params.trailReportArtifact.payload.sourceTaskId ?? 'n/a'}`,
|
||||||
|
`Action count: ${params.trailReportArtifact.payload.actionCount}`,
|
||||||
|
`Safety signals: ${params.trailReportArtifact.payload.safetySignalCount}`,
|
||||||
|
'',
|
||||||
|
'## Action Breakdown',
|
||||||
|
breakdown || '- none',
|
||||||
|
'',
|
||||||
|
'## Evidence',
|
||||||
|
evidence || '- none',
|
||||||
|
].join('\n');
|
||||||
|
const excerpt = `${params.trailReportArtifact.payload.actionCount} audited actions imported from Cowork`;
|
||||||
|
|
||||||
|
const note: NoteDoc = {
|
||||||
|
id: noteId,
|
||||||
|
productId: 'notelett',
|
||||||
|
workspaceId: params.workspaceId,
|
||||||
|
userId: params.userId,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
status: 'draft',
|
||||||
|
tags: ['ecosystem', 'phase3', 'audit'],
|
||||||
|
links: [params.trailReportArtifact.id],
|
||||||
|
sourceType: 'ecosystem-trail-report',
|
||||||
|
sourceUri: params.trailReportArtifact.id,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
createdBy: params.userId,
|
||||||
|
updatedBy: params.userId,
|
||||||
|
agentId: 'phase3-trail-importer',
|
||||||
|
};
|
||||||
|
|
||||||
|
const noteArtifactDoc: NoteArtifactDoc = {
|
||||||
|
id: `na_${randomUUID().replace(/-/g, '').slice(0, 12)}`,
|
||||||
|
productId: 'notelett',
|
||||||
|
workspaceId: params.workspaceId,
|
||||||
|
userId: params.userId,
|
||||||
|
noteId,
|
||||||
|
artifactType: 'summary',
|
||||||
|
title: 'Imported trail report artifact',
|
||||||
|
description: `Audit trail source ${params.trailReportArtifact.id}`,
|
||||||
|
blobPath: join(params.root ?? getPhase3Root(), 'artifacts', 'note', `${noteArtifactId}.json`),
|
||||||
|
contentType: 'application/json',
|
||||||
|
createdAt: now,
|
||||||
|
createdBy: params.userId,
|
||||||
|
updatedAt: now,
|
||||||
|
updatedBy: params.userId,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ecosystemNoteArtifact: NoteArtifactEnvelope = {
|
||||||
|
id: noteArtifactId,
|
||||||
|
artifactType: 'note',
|
||||||
|
schemaVersion: 1,
|
||||||
|
productId: 'notelett',
|
||||||
|
sourceSurface: 'backend',
|
||||||
|
title,
|
||||||
|
summary: excerpt,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
createdBy: {
|
||||||
|
actorType: 'agent',
|
||||||
|
actorId: 'phase3-trail-importer',
|
||||||
|
},
|
||||||
|
ownership: {
|
||||||
|
userId: params.userId,
|
||||||
|
orgId: params.trailReportArtifact.ownership.orgId ?? null,
|
||||||
|
},
|
||||||
|
visibility: {
|
||||||
|
scope: 'private',
|
||||||
|
allowedProducts: ['learning_multimodal_memory_agents'],
|
||||||
|
},
|
||||||
|
status: 'draft',
|
||||||
|
tags: ['ecosystem', 'phase3', 'audit'],
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
relation: 'summarizes',
|
||||||
|
targetArtifactId: params.trailReportArtifact.id,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
provenance: {
|
||||||
|
...params.trailReportArtifact.provenance,
|
||||||
|
runId,
|
||||||
|
lineage: [
|
||||||
|
...params.trailReportArtifact.provenance.lineage,
|
||||||
|
{
|
||||||
|
stepType: 'audit-note-created',
|
||||||
|
productId: 'notelett',
|
||||||
|
actorType: 'agent',
|
||||||
|
timestamp: now,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
noteFormat: 'markdown',
|
||||||
|
body,
|
||||||
|
excerpt,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const createdEvent: EcosystemEvent = {
|
||||||
|
eventId: `evt_note_created_${randomUUID().replace(/-/g, '').slice(0, 12)}`,
|
||||||
|
eventName: 'artifact.created',
|
||||||
|
eventVersion: 1,
|
||||||
|
occurredAt: now,
|
||||||
|
productId: 'notelett',
|
||||||
|
sourceSurface: 'backend',
|
||||||
|
userId: params.userId,
|
||||||
|
orgId: params.trailReportArtifact.ownership.orgId ?? null,
|
||||||
|
sessionId: params.trailReportArtifact.provenance.sessionId ?? null,
|
||||||
|
runId,
|
||||||
|
artifactId: ecosystemNoteArtifact.id,
|
||||||
|
actor: {
|
||||||
|
actorType: 'agent',
|
||||||
|
actorId: 'phase3-trail-importer',
|
||||||
|
},
|
||||||
|
trace: {
|
||||||
|
correlationId: params.trailReportArtifact.provenance.correlationId ?? null,
|
||||||
|
causationId: params.trailReportCreatedEvent?.eventId ?? null,
|
||||||
|
parentEventId: params.trailReportCreatedEvent?.eventId ?? null,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
artifactType: 'note',
|
||||||
|
title,
|
||||||
|
status: 'draft',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const linkedEvent: EcosystemEvent = {
|
||||||
|
eventId: `evt_note_linked_${randomUUID().replace(/-/g, '').slice(0, 12)}`,
|
||||||
|
eventName: 'artifact.linked',
|
||||||
|
eventVersion: 1,
|
||||||
|
occurredAt: now,
|
||||||
|
productId: 'notelett',
|
||||||
|
sourceSurface: 'backend',
|
||||||
|
userId: params.userId,
|
||||||
|
orgId: params.trailReportArtifact.ownership.orgId ?? null,
|
||||||
|
sessionId: params.trailReportArtifact.provenance.sessionId ?? null,
|
||||||
|
runId,
|
||||||
|
artifactId: ecosystemNoteArtifact.id,
|
||||||
|
actor: {
|
||||||
|
actorType: 'agent',
|
||||||
|
actorId: 'phase3-trail-importer',
|
||||||
|
},
|
||||||
|
trace: {
|
||||||
|
correlationId: params.trailReportArtifact.provenance.correlationId ?? null,
|
||||||
|
causationId: createdEvent.eventId,
|
||||||
|
parentEventId: createdEvent.eventId,
|
||||||
|
},
|
||||||
|
payload: {
|
||||||
|
sourceArtifactId: ecosystemNoteArtifact.id,
|
||||||
|
targetArtifactId: params.trailReportArtifact.id,
|
||||||
|
relation: 'summarizes',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return { note, noteArtifactDoc, ecosystemNoteArtifact, createdEvent, linkedEvent };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function persistPhase3NoteOutputs(params: {
|
||||||
|
ecosystemNoteArtifact: NoteArtifactEnvelope;
|
||||||
|
createdEvent: EcosystemEvent;
|
||||||
|
linkedEvent: EcosystemEvent;
|
||||||
|
root?: string;
|
||||||
|
}) {
|
||||||
|
const root = params.root ?? getPhase3Root();
|
||||||
|
await writeJson(join(root, 'artifacts', 'note', `${params.ecosystemNoteArtifact.id}.json`), params.ecosystemNoteArtifact);
|
||||||
|
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) {
|
||||||
|
await mkdir(dirname(path), { recursive: true });
|
||||||
|
await writeFile(path, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user