UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

279 lines 9.89 kB
/** * Provenance service for W3C PROV tracking * * @module research/services/provenance */ import { promises as fs } from 'fs'; import { join } from 'path'; /** * Provenance service for W3C PROV-compliant tracking */ export class ProvenanceService { recordsDir; autoPersist; records; constructor(config = {}) { this.recordsDir = config.recordsDir || '.aiwg/research/provenance/records'; this.autoPersist = config.autoPersist !== false; this.records = new Map(); } /** * Record a provenance activity */ async recordActivity(activity) { const recordId = this.generateRecordId(); const timestamp = new Date().toISOString(); // Create entity for each output const entities = activity.outputs.map((outputId) => ({ id: outputId, type: 'artifact', attributes: {}, })); // For simplicity, use first output as primary entity const primaryEntity = entities[0] || { id: 'unknown', type: 'artifact', attributes: {}, }; const record = { id: recordId, timestamp, entity: primaryEntity, activity: { id: this.generateActivityId(activity.type), type: activity.type, startedAt: timestamp, endedAt: timestamp, attributes: { description: activity.description, ...activity.metadata, }, }, agent: { id: activity.agentId, type: 'software_agent', attributes: {}, }, relationships: { wasGeneratedBy: this.generateActivityId(activity.type), wasDerivedFrom: activity.inputs, wasAttributedTo: activity.agentId, wasAssociatedWith: activity.agentId, }, }; // Store in memory this.records.set(recordId, record); // Persist if enabled if (this.autoPersist) { await this.persistRecord(record); } return record; } /** * Query lineage (provenance chain) for an entity */ async queryLineage(entityId) { const activities = []; const sources = []; const derived = []; // Search through records for (const record of this.records.values()) { // Check if this record involves the entity if (record.entity.id === entityId || record.relationships.wasDerivedFrom?.includes(entityId)) { // Reconstruct activity const activity = { type: record.activity.type, description: String(record.activity.attributes.description || ''), agentId: record.agent.id, inputs: record.relationships.wasDerivedFrom || [], outputs: [record.entity.id], metadata: record.activity.attributes, }; activities.push({ activity, timestamp: record.timestamp, agent: record.agent.id, }); // Track sources and derived if (record.relationships.wasDerivedFrom && record.relationships.wasDerivedFrom.length === 0) { sources.push(record.entity.id); } if (record.relationships.wasDerivedFrom?.includes(entityId)) { derived.push(record.entity.id); } } } return { entityId, activities, sources, derived, }; } /** * Validate provenance chain integrity */ async validateChain(chainId) { // Load chain const chain = await this.queryLineage(chainId); // Check that all activities are connected const allInputs = new Set(); const allOutputs = new Set(); for (const { activity } of chain.activities) { activity.inputs.forEach((input) => allInputs.add(input)); activity.outputs.forEach((output) => allOutputs.add(output)); } // All inputs (except sources) should be outputs of other activities for (const input of allInputs) { if (!chain.sources.includes(input) && !allOutputs.has(input)) { return false; // Broken chain } } return true; } /** * Export provenance in specified format */ async exportPROV(format) { if (format === 'json') { return this.exportJSON(); } else if (format === 'turtle') { return this.exportTurtle(); } throw new Error(`Unsupported format: ${format}`); } /** * Export as PROV-JSON */ exportJSON() { const provDoc = { prefix: { prov: 'http://www.w3.org/ns/prov#', aiwg: 'urn:aiwg:', }, entity: {}, activity: {}, agent: {}, wasGeneratedBy: {}, wasDerivedFrom: {}, wasAttributedTo: {}, wasAssociatedWith: {}, }; for (const record of this.records.values()) { // Add entity provDoc.entity[record.entity.id] = record.entity.attributes; // Add activity provDoc.activity[record.activity.id] = { 'prov:startedAtTime': record.activity.startedAt, 'prov:endedAtTime': record.activity.endedAt, ...record.activity.attributes, }; // Add agent provDoc.agent[record.agent.id] = record.agent.attributes; // Add relationships if (record.relationships.wasGeneratedBy) { provDoc.wasGeneratedBy[record.entity.id] = { 'prov:entity': record.entity.id, 'prov:activity': record.relationships.wasGeneratedBy, }; } if (record.relationships.wasDerivedFrom) { for (const source of record.relationships.wasDerivedFrom) { provDoc.wasDerivedFrom[`${record.entity.id}-${source}`] = { 'prov:generatedEntity': record.entity.id, 'prov:usedEntity': source, }; } } if (record.relationships.wasAttributedTo) { provDoc.wasAttributedTo[record.entity.id] = { 'prov:entity': record.entity.id, 'prov:agent': record.relationships.wasAttributedTo, }; } if (record.relationships.wasAssociatedWith) { provDoc.wasAssociatedWith[record.activity.id] = { 'prov:activity': record.activity.id, 'prov:agent': record.relationships.wasAssociatedWith, }; } } return JSON.stringify(provDoc, null, 2); } /** * Export as PROV-Turtle (simplified) */ exportTurtle() { const lines = [ '@prefix prov: <http://www.w3.org/ns/prov#> .', '@prefix aiwg: <urn:aiwg:> .', '', ]; for (const record of this.records.values()) { // Entity lines.push(`aiwg:${record.entity.id} a prov:Entity .`); // Activity lines.push(`aiwg:${record.activity.id} a prov:Activity ;`); lines.push(` prov:startedAtTime "${record.activity.startedAt}"^^xsd:dateTime ;`); lines.push(` prov:endedAtTime "${record.activity.endedAt}"^^xsd:dateTime .`); // Agent lines.push(`aiwg:${record.agent.id} a prov:Agent .`); // Relationships if (record.relationships.wasGeneratedBy) { lines.push(`aiwg:${record.entity.id} prov:wasGeneratedBy aiwg:${record.relationships.wasGeneratedBy} .`); } if (record.relationships.wasDerivedFrom) { for (const source of record.relationships.wasDerivedFrom) { lines.push(`aiwg:${record.entity.id} prov:wasDerivedFrom aiwg:${source} .`); } } if (record.relationships.wasAttributedTo) { lines.push(`aiwg:${record.entity.id} prov:wasAttributedTo aiwg:${record.relationships.wasAttributedTo} .`); } if (record.relationships.wasAssociatedWith) { lines.push(`aiwg:${record.activity.id} prov:wasAssociatedWith aiwg:${record.relationships.wasAssociatedWith} .`); } lines.push(''); } return lines.join('\n'); } /** * Persist record to disk */ async persistRecord(record) { const filename = `${record.id}.json`; const filepath = join(this.recordsDir, filename); // Ensure directory exists await this.ensureDir(this.recordsDir); await fs.writeFile(filepath, JSON.stringify(record, null, 2), 'utf-8'); } /** * Generate record ID */ generateRecordId() { return `prov-${Date.now()}-${Math.random().toString(36).substring(7)}`; } /** * Generate activity ID */ generateActivityId(type) { return `activity-${type}-${Date.now()}`; } /** * Ensure directory exists */ async ensureDir(dir) { try { await fs.mkdir(dir, { recursive: true }); } catch (error) { if (error.code !== 'EEXIST') { throw error; } } } } //# sourceMappingURL=provenance.js.map