chittycan
Version:
Your completely autonomous network that grows with you - DNA ownership platform with encrypted vaults, PDX portability, and ChittyFoundation governance
287 lines • 10.2 kB
JavaScript
import crypto from 'crypto';
import fs from 'fs-extra';
import path from 'path';
import { DNAVault } from './dna-vault.js';
import { loadConfig, CONFIG_DIR } from './config.js';
export async function exportDNA(options) {
const vault = DNAVault.getInstance();
const dna = await vault.load();
if (!dna) {
throw new Error('No DNA found in vault. Start using ChittyCan to build your DNA!');
}
const config = loadConfig();
// Load package.json for version
const packageJsonPath = path.join(process.cwd(), 'package.json');
let packageJson = { version: '0.5.0' };
if (fs.existsSync(packageJsonPath)) {
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
}
// Apply privacy mode
const processedDNA = applyPrivacyMode(dna, options.privacy);
// Generate integrity hash
const dnaJson = JSON.stringify(processedDNA);
const hash = crypto.createHash('sha256').update(dnaJson).digest('hex');
// Sign with private key
const signature = signData(dnaJson);
const pdxExport = {
'@context': 'https://foundation.chitty.cc/pdx/v1',
'@type': 'ChittyDNA',
version: '1.0.0',
owner: {
email: config.user?.email,
consent: {
learning: true,
portability: true,
attribution: options.includeAttribution,
marketplace: false,
timestamp: new Date().toISOString(),
signature: signature
},
license: {
type: 'CDCL-1.0',
grant: 'revocable',
scope: ['personal'],
expires: null
}
},
dna: processedDNA,
metadata: {
created: dna.workflows[0]?.created || new Date().toISOString(),
last_modified: new Date().toISOString(),
export_timestamp: new Date().toISOString(),
export_tool: {
name: 'chittycan',
version: packageJson.version,
url: 'https://github.com/chittycorp/chittycan'
},
format_version: 'pdx-1.0',
schema_url: 'https://foundation.chitty.cc/pdx/v1/schema.json',
integrity: {
algorithm: 'sha256',
hash,
signature
}
}
};
if (options.includeAttribution) {
pdxExport.attribution = await loadAttribution();
}
// Log export event
await logExportEvent(options.privacy);
return pdxExport;
}
function applyPrivacyMode(dna, mode) {
if (mode === 'full') {
return dna; // No changes
}
if (mode === 'hash-only') {
return {
...dna,
workflows: dna.workflows.map(wf => ({
...wf,
pattern: {
...wf.pattern,
value: wf.pattern.hash // Replace pattern with hash
},
privacy: {
...wf.privacy,
reveal_pattern: false
}
})),
context_memory: dna.context_memory.map(ctx => ({
...ctx,
privacy: {
...ctx.privacy,
reveal_content: false
}
}))
};
}
throw new Error('ZK mode not yet implemented (v2.0)');
}
function signData(data) {
// Simple signature using hash for now
// TODO: Implement RSA signature with private key
const hash = crypto.createHash('sha256').update(data).digest('hex');
return `0x${hash}`;
}
async function loadAttribution() {
const attributionPath = path.join(CONFIG_DIR, 'attribution', 'chains.jsonl');
if (!fs.existsSync(attributionPath)) {
return {
enabled: false,
contributions: []
};
}
const lines = fs.readFileSync(attributionPath, 'utf8').trim().split('\n');
const contributions = lines
.filter((line) => line.trim())
.map((line) => JSON.parse(line));
return {
enabled: true,
contributions
};
}
async function logExportEvent(privacyMode) {
const auditPath = path.join(CONFIG_DIR, 'audit', 'learning-events.jsonl');
const entry = {
timestamp: new Date().toISOString(),
event: 'dna_exported',
privacy_mode: privacyMode
};
fs.appendFileSync(auditPath, JSON.stringify(entry) + '\n');
}
export async function importDNA(pdxJson, options) {
const pdx = JSON.parse(pdxJson);
// Validate schema
if (pdx['@type'] !== 'ChittyDNA') {
throw new Error('Invalid PDX file: @type must be ChittyDNA');
}
// Verify integrity
const dnaJson = JSON.stringify(pdx.dna);
const hash = crypto.createHash('sha256').update(dnaJson).digest('hex');
if (hash !== pdx.metadata.integrity.hash) {
throw new Error('Integrity check failed: hash mismatch');
}
// Verify signature
if (options.verifySignature) {
const expectedSignature = signData(dnaJson);
// Note: In production, verify with public key
// For now, we just check the hash-based signature
}
// Check consent
if (!pdx.owner.consent.portability) {
throw new Error('Portability not consented');
}
// Load existing DNA
const vault = DNAVault.getInstance();
const existingDNA = await vault.load() || {
workflows: [],
preferences: {},
command_templates: [],
integrations: [],
context_memory: []
};
// Merge workflows
const { merged, errors } = mergeWorkflows(existingDNA.workflows, pdx.dna.workflows, options.conflictResolution);
// Merge templates
const mergedTemplates = mergeTemplates(existingDNA.command_templates, pdx.dna.command_templates, options.conflictResolution);
// Merge integrations
const mergedIntegrations = mergeIntegrations(existingDNA.integrations, pdx.dna.integrations, options.conflictResolution);
const newDNA = {
...existingDNA,
workflows: merged,
preferences: { ...existingDNA.preferences, ...pdx.dna.preferences },
command_templates: mergedTemplates,
integrations: mergedIntegrations
};
await vault.save(newDNA);
// Log import event
await logImportEvent(pdx.dna.workflows.length);
return {
success: true,
patterns: pdx.dna.workflows.length,
errors
};
}
function mergeWorkflows(existing, incoming, resolution) {
const merged = [...existing];
const errors = [];
for (const workflow of incoming) {
const conflictIndex = existing.findIndex(wf => wf.id === workflow.id);
if (conflictIndex === -1) {
merged.push(workflow);
continue;
}
const conflict = existing[conflictIndex];
// Handle conflict
if (resolution === 'merge') {
// Combine usage counts, take higher confidence
merged[conflictIndex] = {
...workflow,
usage_count: conflict.usage_count + workflow.usage_count,
confidence: Math.max(conflict.confidence, workflow.confidence),
impact: {
time_saved: conflict.impact.time_saved + workflow.impact.time_saved
}
};
}
else if (resolution === 'replace') {
merged[conflictIndex] = workflow;
}
else if (resolution === 'rename') {
merged.push({
...workflow,
id: `${workflow.id}_imported`,
name: `${workflow.name} (imported)`
});
}
else if (resolution === 'skip') {
errors.push(`Skipped conflicting workflow: ${workflow.id}`);
}
}
return { merged, errors };
}
function mergeTemplates(existing, incoming, resolution) {
const merged = [...existing];
for (const template of incoming) {
const exists = existing.find(t => t.id === template.id);
if (!exists) {
merged.push(template);
}
else if (resolution === 'replace') {
const index = merged.findIndex(t => t.id === template.id);
merged[index] = template;
}
}
return merged;
}
function mergeIntegrations(existing, incoming, resolution) {
const merged = [...existing];
for (const integration of incoming) {
const exists = existing.find(i => i.name === integration.name && i.type === integration.type);
if (!exists) {
merged.push(integration);
}
else if (resolution === 'replace') {
const index = merged.findIndex(i => i.name === integration.name && i.type === integration.type);
merged[index] = integration;
}
}
return merged;
}
async function logImportEvent(patternCount) {
const auditPath = path.join(CONFIG_DIR, 'audit', 'learning-events.jsonl');
const entry = {
timestamp: new Date().toISOString(),
event: 'dna_imported',
pattern_count: patternCount
};
fs.appendFileSync(auditPath, JSON.stringify(entry) + '\n');
}
/**
* Check export rate limit (1 per 24 hours for Bronze tier)
*/
export async function checkExportRateLimit() {
const auditPath = path.join(CONFIG_DIR, 'audit', 'learning-events.jsonl');
if (!fs.existsSync(auditPath)) {
return { allowed: true };
}
const lines = fs.readFileSync(auditPath, 'utf8').trim().split('\n');
const exports = lines
.filter((line) => line.trim())
.map((line) => JSON.parse(line))
.filter((entry) => entry.event === 'dna_exported');
if (exports.length === 0) {
return { allowed: true };
}
const lastExport = new Date(exports[exports.length - 1].timestamp);
const now = new Date();
const hoursSinceLastExport = (now.getTime() - lastExport.getTime()) / (1000 * 60 * 60);
if (hoursSinceLastExport < 24) {
const nextAllowed = new Date(lastExport.getTime() + 24 * 60 * 60 * 1000);
return { allowed: false, nextAllowed };
}
return { allowed: true };
}
//# sourceMappingURL=pdx.js.map