hololake-system-architecture/product-source/guanghu-knowledge-base/server/channel-runtime.ts

126 lines
4.2 KiB
TypeScript

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<ChannelState>;
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,
};
}