import crypto from 'crypto'; export const KNOWLEDGE_MODULE_ID = 'HL-MOD-KNOWLEDGE-001'; export interface ModuleManifest { id: string; name: string; version: string; kind: 'native-application'; state: 'LIVE'; description: string; capabilities: string[]; dataPolicy: string; } export interface ChannelModuleState { id: string; installed: boolean; mounted: boolean; order: number; } export interface ChannelState { schema: 'hololake.personal-channel-state/v1'; channelId: string; revision: number; updatedAt: string; modules: ChannelModuleState[]; } export interface ChannelPatch { operation: 'set_module_state'; moduleId: string; installed?: boolean; mounted?: boolean; } export interface ChannelReceipt { schema: 'hololake.channel-receipt/v1'; id: string; operation: ChannelPatch; before: ChannelState; after: ChannelState; createdAt: string; reversible: true; } export const MODULE_REGISTRY: ModuleManifest[] = [{ id: KNOWLEDGE_MODULE_ID, name: '知识库', version: '0.8.0', kind: 'native-application', state: 'LIVE', description: 'Git 驱动的个人知识空间,支持导入、编辑、检索、历史与 HoloLake 语言操作。', capabilities: [ 'knowledge.import', 'knowledge.read', 'knowledge.search', 'knowledge.edit', 'knowledge.history', ], dataPolicy: '移除模块只卸载频道入口,不删除用户文档、历史或服务器绑定。', }]; export function defaultChannelState(now = new Date().toISOString()): ChannelState { return { schema: 'hololake.personal-channel-state/v1', channelId: 'CHANNEL-PERSONAL-DEFAULT', revision: 0, updatedAt: now, modules: [{ id: KNOWLEDGE_MODULE_ID, installed: true, mounted: true, order: 0 }], }; } export function normalizeChannelState(input: unknown): ChannelState { const fallback = defaultChannelState(); if (!input || typeof input !== 'object') return fallback; const candidate = input as Partial; const known = new Set(MODULE_REGISTRY.map(module => module.id)); const modules = Array.isArray(candidate.modules) ? candidate.modules .filter(module => module && known.has(module.id)) .map((module, index) => ({ id: module.id, installed: module.installed !== false, mounted: module.installed !== false && module.mounted !== false, order: Number.isFinite(module.order) ? Number(module.order) : index, })) : fallback.modules; for (const manifest of MODULE_REGISTRY) { if (!modules.some(module => module.id === manifest.id)) { modules.push({ id: manifest.id, installed: false, mounted: false, order: modules.length }); } } return { schema: 'hololake.personal-channel-state/v1', channelId: typeof candidate.channelId === 'string' && candidate.channelId ? candidate.channelId : fallback.channelId, revision: Number.isInteger(candidate.revision) && Number(candidate.revision) >= 0 ? Number(candidate.revision) : 0, updatedAt: typeof candidate.updatedAt === 'string' ? candidate.updatedAt : fallback.updatedAt, modules: modules.sort((a, b) => a.order - b.order), }; } export function applyChannelPatch(currentInput: ChannelState, patch: ChannelPatch, now = new Date().toISOString()): ChannelReceipt { const before = normalizeChannelState(currentInput); if (patch.operation !== 'set_module_state') throw new Error('不支持的频道动作'); if (!MODULE_REGISTRY.some(module => module.id === patch.moduleId)) throw new Error('模块未在光湖注册表登记'); if (typeof patch.installed !== 'boolean' && typeof patch.mounted !== 'boolean') throw new Error('频道动作没有状态变化'); const modules = before.modules.map(module => { if (module.id !== patch.moduleId) return module; const installed = typeof patch.installed === 'boolean' ? patch.installed : module.installed; const mounted = installed && (typeof patch.mounted === 'boolean' ? patch.mounted : module.mounted); return { ...module, installed, mounted }; }); const after = { ...before, revision: before.revision + 1, updatedAt: now, modules }; return { schema: 'hololake.channel-receipt/v1', id: `HL-CHANNEL-RCPT-${crypto.randomUUID()}`, operation: patch, before, after, createdAt: now, reversible: true, }; }