UNPKG

@chittyos/core

Version:

ChittyOS Core - Essential package with ID, auth, verification, beacon tracking, and brand components for all ChittyOS applications

1 lines 16.9 kB
{"version":3,"sources":["../../src/canon/index.ts"],"sourcesContent":["/**\n * ChittyOS Canon - Source of truth and canonical data management\n * Manages the authoritative state and version control for distributed data\n */\n\nimport { nanoid } from 'nanoid'\nimport * as crypto from 'crypto'\nimport { ChittyID } from '../id'\n\nexport interface CanonicalRecord {\n id: string\n canonId: string\n version: number\n data: any\n schema?: string\n hash: string\n signature?: string\n chittyId: string\n timestamp: string\n previousHash?: string\n metadata?: {\n source: string\n tags: string[]\n ttl?: number\n immutable?: boolean\n }\n}\n\nexport interface CanonConfig {\n endpoint?: string\n enableChain?: boolean\n enableSignatures?: boolean\n storageAdapter?: CanonStorageAdapter\n}\n\nexport interface CanonStorageAdapter {\n get(canonId: string): Promise<CanonicalRecord | null>\n set(record: CanonicalRecord): Promise<void>\n delete(canonId: string): Promise<boolean>\n list(filter?: any): Promise<CanonicalRecord[]>\n}\n\nexport interface CanonValidation {\n valid: boolean\n errors?: string[]\n warnings?: string[]\n}\n\nconst DEFAULT_CONFIG: CanonConfig = {\n endpoint: process.env.CHITTY_CANON_ENDPOINT || 'https://canon.chitty.cc',\n enableChain: true,\n enableSignatures: true\n}\n\nlet config = { ...DEFAULT_CONFIG }\nconst canonCache = new Map<string, CanonicalRecord>()\nconst chainIndex = new Map<string, CanonicalRecord[]>()\n\nexport function configure(customConfig: CanonConfig): void {\n config = { ...config, ...customConfig }\n}\n\n/**\n * Create a new canonical record\n */\nexport function createCanonical(\n data: any,\n chittyId: string,\n metadata?: CanonicalRecord['metadata']\n): CanonicalRecord {\n const canonId = `CANON_${nanoid(21)}`\n const timestamp = new Date().toISOString()\n\n // Calculate data hash\n const hash = crypto\n .createHash('sha256')\n .update(JSON.stringify({ data, chittyId, timestamp }))\n .digest('hex')\n\n const record: CanonicalRecord = {\n id: nanoid(),\n canonId,\n version: 1,\n data,\n hash,\n chittyId,\n timestamp,\n metadata: {\n source: metadata?.source || 'chittyos-core',\n tags: metadata?.tags || [],\n ttl: metadata?.ttl,\n immutable: metadata?.immutable || false\n }\n }\n\n // Store in cache\n canonCache.set(canonId, record)\n\n // Initialize chain if enabled\n if (config.enableChain) {\n chainIndex.set(canonId, [record])\n }\n\n return record\n}\n\n/**\n * Update a canonical record (creates new version)\n */\nexport function updateCanonical(\n canonId: string,\n data: any,\n chittyId: string\n): CanonicalRecord | null {\n const existing = canonCache.get(canonId)\n\n if (!existing) {\n return null\n }\n\n if (existing.metadata?.immutable) {\n throw new Error('Cannot update immutable canonical record')\n }\n\n const timestamp = new Date().toISOString()\n const previousHash = existing.hash\n\n // Calculate new hash including previous hash for chain integrity\n const hash = crypto\n .createHash('sha256')\n .update(JSON.stringify({ data, chittyId, timestamp, previousHash }))\n .digest('hex')\n\n const record: CanonicalRecord = {\n ...existing,\n id: nanoid(),\n version: existing.version + 1,\n data,\n hash,\n previousHash,\n chittyId,\n timestamp\n }\n\n // Update cache\n canonCache.set(canonId, record)\n\n // Update chain if enabled\n if (config.enableChain) {\n const chain = chainIndex.get(canonId) || []\n chain.push(record)\n chainIndex.set(canonId, chain)\n }\n\n return record\n}\n\n/**\n * Get canonical record by ID\n */\nexport async function getCanonical(canonId: string): Promise<CanonicalRecord | null> {\n // Check cache first\n if (canonCache.has(canonId)) {\n return canonCache.get(canonId)!\n }\n\n // Try storage adapter\n if (config.storageAdapter) {\n const record = await config.storageAdapter.get(canonId)\n if (record) {\n canonCache.set(canonId, record)\n return record\n }\n }\n\n // Try remote endpoint\n if (config.endpoint) {\n try {\n const response = await fetch(`${config.endpoint}/canon/${canonId}`)\n if (response.ok) {\n const record = await response.json() as CanonicalRecord\n canonCache.set(canonId, record)\n return record\n }\n } catch (error) {\n console.error('[Canon] Failed to fetch from endpoint:', error)\n }\n }\n\n return null\n}\n\n/**\n * Get version history for a canonical record\n */\nexport function getCanonicalHistory(canonId: string): CanonicalRecord[] {\n return chainIndex.get(canonId) || []\n}\n\n/**\n * Validate canonical record integrity\n */\nexport function validateCanonical(record: CanonicalRecord): CanonValidation {\n const errors: string[] = []\n const warnings: string[] = []\n\n // Validate structure\n if (!record.canonId || !record.canonId.startsWith('CANON_')) {\n errors.push('Invalid canon ID format')\n }\n\n if (!record.hash) {\n errors.push('Missing hash')\n }\n\n if (!record.chittyId) {\n errors.push('Missing ChittyID')\n }\n\n // Validate hash integrity\n const expectedHash = crypto\n .createHash('sha256')\n .update(JSON.stringify({\n data: record.data,\n chittyId: record.chittyId,\n timestamp: record.timestamp,\n ...(record.previousHash && { previousHash: record.previousHash })\n }))\n .digest('hex')\n\n if (record.hash !== expectedHash) {\n errors.push('Hash mismatch - data integrity compromised')\n }\n\n // Validate chain integrity if previous hash exists\n if (record.previousHash && record.version > 1) {\n const history = getCanonicalHistory(record.canonId)\n const previousRecord = history[record.version - 2]\n\n if (previousRecord && previousRecord.hash !== record.previousHash) {\n errors.push('Chain integrity violation - previous hash mismatch')\n }\n }\n\n // Check TTL\n if (record.metadata?.ttl) {\n const age = Date.now() - new Date(record.timestamp).getTime()\n const ttlMs = record.metadata.ttl * 1000\n\n if (age > ttlMs) {\n warnings.push('Record has exceeded TTL')\n }\n }\n\n return {\n valid: errors.length === 0,\n errors: errors.length > 0 ? errors : undefined,\n warnings: warnings.length > 0 ? warnings : undefined\n }\n}\n\n/**\n * Sign a canonical record\n */\nexport function signCanonical(record: CanonicalRecord, privateKey: string): CanonicalRecord {\n if (!config.enableSignatures) {\n return record\n }\n\n const sign = crypto.createSign('SHA256')\n sign.update(record.hash)\n const signature = sign.sign(privateKey, 'base64')\n\n return {\n ...record,\n signature\n }\n}\n\n/**\n * Verify canonical record signature\n */\nexport function verifyCanonicalSignature(\n record: CanonicalRecord,\n publicKey: string\n): boolean {\n if (!record.signature) {\n return false\n }\n\n try {\n const verify = crypto.createVerify('SHA256')\n verify.update(record.hash)\n return verify.verify(publicKey, record.signature, 'base64')\n } catch (error) {\n return false\n }\n}\n\n/**\n * Merge conflicting canonical records\n */\nexport function mergeCanonical(\n records: CanonicalRecord[],\n strategy: 'latest' | 'highest-version' | 'custom' = 'latest',\n customMerge?: (records: CanonicalRecord[]) => any\n): CanonicalRecord {\n if (records.length === 0) {\n throw new Error('No records to merge')\n }\n\n if (records.length === 1) {\n return records[0]\n }\n\n let winner: CanonicalRecord\n\n switch (strategy) {\n case 'latest':\n winner = records.reduce((latest, record) =>\n new Date(record.timestamp) > new Date(latest.timestamp) ? record : latest\n )\n break\n\n case 'highest-version':\n winner = records.reduce((highest, record) =>\n record.version > highest.version ? record : highest\n )\n break\n\n case 'custom':\n if (!customMerge) {\n throw new Error('Custom merge function required')\n }\n const mergedData = customMerge(records)\n winner = createCanonical(\n mergedData,\n records[0].chittyId,\n { source: 'merge', tags: ['merged'] }\n )\n break\n\n default:\n winner = records[0]\n }\n\n // Add merge metadata\n return {\n ...winner,\n metadata: {\n ...winner.metadata,\n source: 'merge',\n tags: [...(winner.metadata?.tags || []), 'merged', `from-${records.length}-records`]\n }\n }\n}\n\n/**\n * Query canonical records\n */\nexport async function queryCanonical(filter: {\n chittyId?: string\n tags?: string[]\n source?: string\n afterTimestamp?: string\n beforeTimestamp?: string\n}): Promise<CanonicalRecord[]> {\n let results: CanonicalRecord[] = []\n\n // Query from cache\n for (const record of canonCache.values()) {\n let matches = true\n\n if (filter.chittyId && record.chittyId !== filter.chittyId) {\n matches = false\n }\n\n if (filter.tags && filter.tags.length > 0) {\n const recordTags = record.metadata?.tags || []\n if (!filter.tags.every(tag => recordTags.includes(tag))) {\n matches = false\n }\n }\n\n if (filter.source && record.metadata?.source !== filter.source) {\n matches = false\n }\n\n if (filter.afterTimestamp && record.timestamp <= filter.afterTimestamp) {\n matches = false\n }\n\n if (filter.beforeTimestamp && record.timestamp >= filter.beforeTimestamp) {\n matches = false\n }\n\n if (matches) {\n results.push(record)\n }\n }\n\n // Query from storage adapter\n if (config.storageAdapter) {\n const storedRecords = await config.storageAdapter.list(filter)\n results = [...results, ...storedRecords]\n }\n\n // Remove duplicates\n const seen = new Set<string>()\n results = results.filter(record => {\n if (seen.has(record.canonId)) {\n return false\n }\n seen.add(record.canonId)\n return true\n })\n\n return results\n}\n\n/**\n * Clear canon cache\n */\nexport function clearCanonCache(): void {\n canonCache.clear()\n chainIndex.clear()\n}\n\n/**\n * Get canon statistics\n */\nexport function getCanonStats(): {\n totalRecords: number\n totalChains: number\n cacheSize: number\n oldestRecord?: string\n newestRecord?: string\n} {\n let oldest: string | undefined\n let newest: string | undefined\n\n for (const record of canonCache.values()) {\n if (!oldest || record.timestamp < oldest) {\n oldest = record.timestamp\n }\n if (!newest || record.timestamp > newest) {\n newest = record.timestamp\n }\n }\n\n return {\n totalRecords: canonCache.size,\n totalChains: chainIndex.size,\n cacheSize: JSON.stringify([...canonCache.values()]).length,\n oldestRecord: oldest,\n newestRecord: newest\n }\n}\n\nexport default {\n configure,\n createCanonical,\n updateCanonical,\n getCanonical,\n getCanonicalHistory,\n validateCanonical,\n signCanonical,\n verifyCanonicalSignature,\n mergeCanonical,\n queryCanonical,\n clearCanonCache,\n getCanonStats\n}"],"mappings":"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","names":[]}