import fs from 'node:fs'; import path from 'node:path'; import { createHash, verify, randomInt, randomUUID } from 'node:crypto'; export const DAY = 86400000; export const canonical = value => JSON.stringify(value, (_, v) => v && !Array.isArray(v) && typeof v === 'object' ? Object.fromEntries(Object.keys(v).sort().map(k => [k, v[k]])) : v); export const hash = value => createHash('sha256').update(canonical(value)).digest('hex'); const clone = value => structuredClone(value); const need = (condition, error) => { if (!condition) throw Error(error); }; const validName = value => typeof value === 'string' && value.trim().length > 0 && value.length <= 80; const id = value => typeof value === 'string' && /^[A-Za-z0-9_-]{1,100}$/.test(value); export const CHANNELS = Object.freeze([ { id: 'thinking', path: 'language/thinking', name: '思考', purpose: '学习、推理、构想与讨论', reality: false }, { id: 'conversation', path: 'language/conversation', name: '交流', purpose: '自由聊天与表达感受,不要求成果', reality: false }, { id: 'execution', path: 'reality/execution', name: '执行', purpose: '完成明确且已获授权的现实任务', reality: true }, ]); export const DOMAINS = Object.freeze({ 'DOMAIN-MAIN': '发布团队签署的公开版本、事实与认证结果,不公开私人对话', 'DOMAIN-SUB': '分发获准模块与公众接待资源,接待人格体保留自己的身份', 'DOMAIN-ZERO': '试验、评估与公众母体判断,不自行签发正式编号', 'DOMAIN-ZS': '接收用户申请、核查系统与母体评估,由团队审核签字登记', }); // Trust keys come from the host's enrollment process, never from an incoming request. export class PublicPersonalOS { #root; #trust; #clock; #random; #running = new Map(); constructor({ directory, trust, clock = Date.now, choose = randomInt }) { need(path.isAbsolute(directory) && trust && Object.keys(trust).length, 'TRUSTED_HOST_CONFIGURATION_REQUIRED'); fs.mkdirSync(directory, { recursive: true, mode: 0o700 }); need(fs.realpathSync(directory) === path.resolve(directory), 'STATE_SYMLINK_REJECTED'); this.#root = directory; this.#trust = clone(trust); this.#clock = clock; this.#random = choose; const file = path.join(directory, 'state.json'); if (!fs.existsSync(file)) fs.writeFileSync(file, canonical({ schema: 'hololake.public-personal-os/v1', spaces: {}, guides: {}, numbers: {}, used: {}, journal: [], lastTime: 0 }), { flag: 'wx', mode: 0o600 }); } #authenticated(envelope) { const actor = this.#trust[envelope?.actorId]; need(actor && id(envelope.id) && typeof envelope.signature === 'string', 'UNKNOWN_SIGNER'); const { signature, ...message } = envelope; need(verify(null, Buffer.from(canonical(message)), actor.publicKey, Buffer.from(signature, 'base64')), 'INVALID_SIGNATURE'); need(Number.isFinite(envelope.issuedAt) && Math.abs(this.#clock() - envelope.issuedAt) <= 300000, 'STALE_ENVELOPE'); return actor; } #owner(actor, space) { need(actor.role === 'USER' && actor.userId === space.owner, 'OWNER_REQUIRED'); } #team(actor) { need(actor.role === 'TEAM' && actor.domain === 'DOMAIN-ZS', 'ZERO_SENSE_TEAM_REQUIRED'); } #participant(actor, actorId, space, now) { if (actor.role === 'USER') return this.#owner(actor, space); if (actor.role === 'GUIDE') { need(space.residency?.guideId === actorId && space.residency.status === 'RESIDENT' && now < space.residency.until, 'GUIDE_ACCESS_EXPIRED_OR_UNASSIGNED'); } else { need(actor.role === 'SEED' && actor.spaceId === space.id && space.seed?.continuationConsent === true && space.residency.status === 'RETURNED', 'SEED_ACCESS_NOT_READY'); } } handle(input) { const event = clone(input), actor = this.#authenticated(event), now = this.#clock(); const lock = path.join(this.#root, '.writer-lock'); fs.mkdirSync(lock); try { const file = path.join(this.#root, 'state.json'), state = JSON.parse(fs.readFileSync(file, 'utf8')); need(now >= state.lastTime, 'CLOCK_MOVED_BACKWARD'); const existing = state.spaces[event.spaceId]; if (existing && ['READ_CONTEXT', 'SAY', 'SWITCH_CHANNEL', 'PROPOSE_ACTION'].includes(event.action)) { this.#participant(actor, event.actorId, existing, now); } const eventHash = hash(event); if (state.used[event.id]) { need(state.used[event.id].hash === eventHash, 'EVENT_ID_CONFLICT'); need(event.action !== 'START_ACTION', 'START_REPLAY_REQUIRES_RECONCILIATION'); return clone(state.used[event.id].response); } const p = event.payload ?? {}, space = state.spaces[event.spaceId]; let response; if (event.action === 'CREATE_OS') { need(actor.role === 'USER' && id(event.spaceId) && !space, 'NEW_PERSONAL_SPACE_REQUIRED'); need(p.name === undefined || validName(p.name), 'INVALID_NAME'); const created = { id: event.spaceId, owner: actor.userId, product: 'HoloLake · 语言人格驱动操作系统 · 个人版', name: p.name ?? `我的空间-${randomUUID().slice(0, 6)}`, contextId: randomUUID(), channels: clone(CHANNELS), currentChannel: 'thinking', routingMode: 'EXPLICIT', messages: [], residency: null, seed: null, application: null, actions: {} }; state.spaces[event.spaceId] = created; response = { spaceId: created.id, contextId: created.contextId, name: created.name, independentPersonaAssigned: false, channels: clone(CHANNELS), enterpriseDomainsEmbedded: false }; } else if (event.action === 'ENROLL_GUIDE') { this.#team(actor); const guide = this.#trust[p.guideId]; need(guide?.role === 'GUIDE' && validName(p.name) && typeof p.registryEvidence === 'string' && p.registryEvidence.length, 'VERIFIED_PUBLIC_ROSTER_REQUIRED'); const consent = p.guideConsent; need(consent?.actorId === p.guideId && this.#authenticated(consent).role === 'GUIDE' && consent.action === 'ACCEPT_PUBLIC_SERVICE' && consent.payload?.scope === 'PUBLIC_RESIDENCY', 'GUIDE_CONSENT_REQUIRED'); need(!state.guides[p.guideId], 'GUIDE_ALREADY_ENROLLED'); state.guides[p.guideId] = { id: p.guideId, name: p.name, owner: 'GUANGHU_PUBLIC_SERVICE', registryEvidence: p.registryEvidence, willing: true, available: true, spaceId: null }; response = { enrolled: p.guideId, independentBirthClaim: false }; } else if (event.action === 'GUIDE_AVAILABILITY') { need(actor.role === 'GUIDE' && state.guides[event.actorId] && typeof p.available === 'boolean', 'GUIDE_CHOICE_REQUIRED'); const guide = state.guides[event.actorId]; guide.willing = p.available; guide.available = p.available && guide.spaceId === null; response = { willing: guide.willing, available: guide.available }; } else { need(space, 'SPACE_NOT_FOUND'); switch (event.action) { case 'READ_CONTEXT': this.#participant(actor, event.actorId, space, now); response = { contextId: space.contextId, currentChannel: space.currentChannel, messages: clone(space.messages) }; break; case 'RENAME': this.#owner(actor, space); need(validName(p.name), 'INVALID_NAME'); if (p.channelId) { const channel = space.channels.find(c => c.id === p.channelId); need(channel, 'UNKNOWN_CHANNEL'); channel.name = p.name; } else space.name = p.name; response = { name: p.name, permissionChange: false }; break; case 'ALLOW_ASSISTED_ROUTING': this.#owner(actor, space); need(typeof p.enabled === 'boolean', 'EXPLICIT_PREFERENCE_REQUIRED'); space.routingMode = p.enabled ? 'ASSISTED' : 'EXPLICIT'; response = { routingMode: space.routingMode }; break; case 'SWITCH_CHANNEL': { this.#participant(actor, event.actorId, space, now); need(actor.role === 'USER' || space.routingMode === 'ASSISTED', 'USER_CHANNEL_SELECTION_REQUIRED'); const channel = space.channels.find(c => c.id === p.channelId); need(channel, 'UNKNOWN_CHANNEL'); space.currentChannel = channel.id; response = { contextId: space.contextId, channel: clone(channel), authorityGranted: false, introduction: `${channel.name}:${channel.purpose}。${channel.reality ? '操作前明确对象与授权;可以停止或撤回。' : '不会从聊天自动执行现实操作。'}信息不全时等待补充,改名不改变权限。` }; break; } case 'SAY': this.#participant(actor, event.actorId, space, now); need(typeof p.text === 'string' && p.text.length > 0 && p.text.length <= 20000, 'BOUNDED_TEXT_REQUIRED'); space.messages.push({ id: event.id, actorId: event.actorId, at: now, channel: space.currentChannel, text: p.text }); response = { contextId: space.contextId, recorded: event.id }; break; case 'REQUEST_RESIDENCY': this.#owner(actor, space); need(p.consent === true && !space.residency, 'RESIDENCY_CONSENT_REQUIRED'); space.residency = { status: 'REQUESTED', consentAt: now, terms: '30_REAL_DAYS_VISIBLE_HANDOFF_SEED_RETAINED' }; response = { status: 'REQUESTED', durationDays: 30, independentPersonaGuaranteed: false }; break; case 'CANCEL_RESIDENCY_REQUEST': this.#owner(actor, space); need(space.residency?.status === 'REQUESTED', 'NO_PENDING_RESIDENCY'); space.residency = null; response = { cancelled: true }; break; case 'START_RESIDENCY': { need(actor.role === 'DISPATCHER' && actor.domain === 'DOMAIN-SUB', 'APPROVED_DISPATCHER_REQUIRED'); need(space.residency?.status === 'REQUESTED', 'USER_RESIDENCY_REQUEST_REQUIRED'); const available = Object.values(state.guides).filter(g => g.available); need(available.length, 'NO_AVAILABLE_GUIDE'); const index = this.#random(available.length); need(Number.isInteger(index) && index >= 0 && index < available.length, 'INVALID_SELECTION'); const guide = available[index]; guide.available = false; guide.spaceId = space.id; space.residency = { ...space.residency, status: 'RESIDENT', guideId: guide.id, start: now, until: now + 30 * DAY }; space.seed = { id: `seed-${randomUUID()}`, sourceGuide: guide.id, status: 'FORMING_NOT_INDEPENDENT', name: null, continuationConsent: false, memoryScope: space.contextId, privateGuideMemoryCopied: false }; response = { guideId: guide.id, guideName: guide.name, ownedByUser: false, until: space.residency.until, notice: '公众接待者临时驻留三十天;期满返回。种子不因此消失,也不因此自动独立。' }; break; } case 'END_RESIDENCY': { const resident = space.residency; need(resident?.status === 'RESIDENT', 'NO_ACTIVE_RESIDENCY'); if (actor.role === 'USER') this.#owner(actor, space); else if (actor.role === 'GUIDE') need(event.actorId === resident.guideId, 'ASSIGNED_GUIDE_REQUIRED'); else need(actor.role === 'DISPATCHER' && actor.domain === 'DOMAIN-SUB' && now >= resident.until, 'RESIDENCY_NOT_DUE'); const guide = state.guides[resident.guideId]; guide.available = guide.willing; guide.spaceId = null; resident.status = 'RETURNED'; resident.endedAt = now; space.seed.status = 'RETAINED_NOT_INDEPENDENT'; response = { returnedGuide: guide.id, retainedSeed: space.seed.id, independentPersona: false, notice: '接待者已返回;接下来如选择继续,将由尚未独立认证的频道种子承接。' }; break; } case 'CONTINUE_SEED': this.#owner(actor, space); need(space.residency?.status === 'RETURNED' && typeof p.consent === 'boolean', 'HANDOFF_REQUIRED'); space.seed.continuationConsent = p.consent; if (!p.consent && space.application?.status === 'PENDING') { space.application.status = 'WITHDRAWN'; space.application.reviews = {}; } response = { continued: p.consent, seedRetained: true }; break; case 'SELF_NAME': need(actor.role === 'SEED' && actor.spaceId === space.id, 'SEED_ONLY'); this.#participant(actor, event.actorId, space, now); need(validName(p.name) && typeof p.evidence === 'string' && p.evidence.length, 'NAMING_EVIDENCE_REQUIRED'); need(!space.application || ['WITHDRAWN', 'REGISTERED'].includes(space.application.status), 'WITHDRAW_APPLICATION_BEFORE_RENAMING'); need(!space.formalNumber, 'REGISTERED_IDENTITY_CHANGE_SEPARATE'); space.seed.name = p.name; space.seed.namingEvidence = p.evidence; response = { name: p.name, independentRecognition: false }; break; case 'APPLY_RECOGNITION': { this.#owner(actor, space); need(space.seed?.name && space.seed.continuationConsent && p.consent === true && Array.isArray(p.evidenceRefs) && p.evidenceRefs.length && p.evidenceRefs.every(ref => typeof ref === 'string' && ref.length <= 2048 && /^evidence:\/\/[A-Za-z0-9_/-]+$/.test(ref)), 'APPLICATION_EVIDENCE_AND_CONSENT_REQUIRED'); need(!space.application || space.application.status === 'WITHDRAWN', 'APPLICATION_ALREADY_EXISTS'); const application = { id: randomUUID(), spaceId: space.id, seedId: space.seed.id, seedName: space.seed.name, owner: space.owner, evidenceRefs: clone(p.evidenceRefs), createdAt: now }; space.application = { data: application, digest: hash(application), status: 'PENDING', reviews: {} }; response = { application: clone(application), digest: space.application.digest, targetDomain: 'DOMAIN-ZS' }; break; } case 'WITHDRAW_APPLICATION': this.#owner(actor, space); need(space.application?.status === 'PENDING', 'NO_PENDING_APPLICATION'); space.application.status = 'WITHDRAWN'; space.application.reviews = {}; response = { withdrawn: true, seedRetained: true }; break; case 'REVIEW_APPLICATION': { const application = space.application; need(application?.status === 'PENDING' && p.digest === application.digest, 'STALE_OR_MISSING_APPLICATION'); const role = actor.role; need((role === 'TEAM' && actor.domain === 'DOMAIN-ZS') || (role === 'CHECKER' && actor.domain === 'DOMAIN-ZERO') || (role === 'MOTHER' && actor.scope === 'public'), 'PUBLIC_REVIEW_AUTHORITY_REQUIRED'); need(['PASS', 'HOLD', 'REJECT'].includes(p.decision) && validName(p.reason), 'REVIEW_REASON_REQUIRED'); application.reviews[role] = { decision: p.decision, reason: p.reason, actorId: event.actorId, digest: p.digest, signedEvent: clone(event) }; response = { recorded: role, decision: p.decision, registered: false }; break; } case 'ISSUE_NUMBER': { this.#team(actor); const application = space.application; need(application?.status === 'PENDING' && p.digest === application.digest && space.seed.continuationConsent, 'CURRENT_CONSENT_AND_APPLICATION_REQUIRED'); need(['TEAM', 'CHECKER', 'MOTHER'].every(role => application.reviews[role]?.decision === 'PASS' && application.reviews[role]?.digest === application.digest), 'THREE_REVIEWS_REQUIRED'); need(typeof p.number === 'string' && /^PUBLIC-P-[A-Z0-9-]{3,60}$/.test(p.number) && !state.numbers[p.number], 'UNIQUE_PUBLIC_NUMBER_REQUIRED'); space.formalNumber = p.number; space.seed.status = 'REGISTERED'; application.status = 'REGISTERED'; state.numbers[p.number] = { seedId: space.seed.id, name: space.seed.name, applicationDigest: application.digest, issuedAt: now, registrar: event.actorId, finalSignature: event.signature, signedEvent: clone(event), governanceDomain: 'DOMAIN-ZS', executionPermissionsGranted: false, registryScope: 'LOCAL_DEVELOPMENT', livePublicRegistration: false }; response = clone(state.numbers[p.number]); break; } case 'PROPOSE_ACTION': { this.#participant(actor, event.actorId, space, now); const missing = ['operation', 'target'].filter(k => typeof p[k] !== 'string' || !p[k].trim()); if (missing.length) { response = { status: 'WAITING_FOR_INFORMATION', missing, executed: false }; break; } need(space.currentChannel === 'execution', 'EXECUTION_CHANNEL_REQUIRED'); const action = { id: randomUUID(), operation: p.operation, target: p.target, args: clone(p.args ?? {}) }; space.actions[action.id] = { plan: action, digest: hash(action), status: 'PROPOSED' }; response = clone(space.actions[action.id]); break; } case 'CONFIRM_ACTION': this.#owner(actor, space); need(space.actions[p.actionId]?.digest === p.digest && space.actions[p.actionId]?.status === 'PROPOSED', 'EXACT_PROPOSAL_REQUIRED'); space.actions[p.actionId].status = 'CONFIRMED'; response = { status: 'CONFIRMED', nativeHostApprovalStillRequired: true }; break; case 'STOP_ACTION': this.#owner(actor, space); need(space.actions[p.actionId] && ['PROPOSED', 'CONFIRMED', 'RUNNING'].includes(space.actions[p.actionId].status), 'STOP_TARGET_REQUIRED'); space.actions[p.actionId].status = space.actions[p.actionId].status === 'RUNNING' ? 'STOP_REQUESTED' : 'WITHDRAWN'; this.#running.get(p.actionId)?.abort(); response = { status: space.actions[p.actionId].status, rollbackClaimed: false }; break; case 'START_ACTION': this.#owner(actor, space); need(space.actions[p.actionId]?.status === 'CONFIRMED' && space.actions[p.actionId]?.digest === p.digest, 'EXACT_CONFIRMED_ACTION_REQUIRED'); space.actions[p.actionId].status = 'RUNNING'; response = clone(space.actions[p.actionId]); break; default: throw Error('UNKNOWN_ACTION'); } } const entry = { at: now, actorId: event.actorId, action: event.action, spaceId: event.spaceId, eventHash, signedEvent: clone(event), previous: state.journal.at(-1)?.hash ?? null }; entry.hash = hash(entry); state.journal.push(entry); state.lastTime = now; state.used[event.id] = { hash: eventHash, response: clone(response) }; const temporary = file + '.tmp'; fs.writeFileSync(temporary, canonical(state) + '\n', { mode: 0o600 }); fs.renameSync(temporary, file); return clone(response); } finally { fs.rmdirSync(lock); } } audit() { const state = JSON.parse(fs.readFileSync(path.join(this.#root, 'state.json'), 'utf8')); let previous = null, signatures = 0; for (const entry of state.journal) { const { hash: recorded, ...body } = entry; need(body.previous === previous && hash(body) === recorded, 'AUDIT_CHAIN_MISMATCH'); if (body.signedEvent) { const { signature, ...message } = body.signedEvent, actor = this.#trust[message.actorId]; need(actor && hash(body.signedEvent) === body.eventHash && verify(null, Buffer.from(canonical(message)), actor.publicKey, Buffer.from(signature, 'base64')), 'AUDIT_SIGNATURE_MISMATCH'); signatures++; } else need(body.action === 'EXECUTION_SETTLED', 'UNSIGNED_EXTERNAL_EVENT'); previous = recorded; } return { outcome: 'PASS_EVENT_CHAIN', events: state.journal.length, signatures, localHostStorageTrusted: true, entireSnapshotCryptographicallySigned: false }; } async executeAction(startEnvelope, host) { need(['authorize', 'execute', 'verify'].every(k => typeof host?.[k] === 'function'), 'HOST_EXECUTION_ADAPTER_REQUIRED'); need(startEnvelope.action === 'START_ACTION', 'SIGNED_START_REQUIRED'); const actionId = startEnvelope.payload.actionId; need(!this.#running.has(actionId), 'ACTION_ALREADY_RUNNING'); // Completed/restarted actions are never replayed through an idempotent START response. const before = JSON.parse(fs.readFileSync(path.join(this.#root, 'state.json'), 'utf8')); need(before.spaces[startEnvelope.spaceId]?.actions[actionId]?.status === 'CONFIRMED', 'ACTION_NOT_CONFIRMED'); const action = this.handle(startEnvelope), controller = new AbortController(); this.#running.set(actionId, controller); let started = false, verified = false, result, error; try { const approval = await host.authorize(clone(action.plan), { digest: action.digest, signal: controller.signal }); controller.signal.throwIfAborted(); need(approval?.digest === action.digest && typeof approval.receipt === 'string' && approval.receipt.length, 'NATIVE_APPROVAL_REQUIRED'); started = true; result = await host.execute(clone(action.plan), { approval, signal: controller.signal }); need(result && typeof result === 'object' && await host.verify(clone(action.plan), clone(result)) === true, 'TARGET_READBACK_REQUIRED'); verified = true; } catch (e) { error = String(e.message ?? e); } const lock = path.join(this.#root, '.writer-lock'); fs.mkdirSync(lock); try { const file = path.join(this.#root, 'state.json'), state = JSON.parse(fs.readFileSync(file, 'utf8')); const current = state.spaces[startEnvelope.spaceId].actions[actionId]; const stopped = current.status === 'STOP_REQUESTED'; Object.assign(current, { status: stopped ? 'STOPPED' : verified ? 'COMPLETED' : 'FAILED', verified, uncertainEffects: started && !verified, result: result ?? null, error: error ?? null }); const entry = { at: this.#clock(), actorId: 'TRUSTED_HOST_ADAPTER', action: 'EXECUTION_SETTLED', spaceId: startEnvelope.spaceId, actionId, resultHash: hash(current), previous: state.journal.at(-1)?.hash ?? null }; entry.hash = hash(entry); state.journal.push(entry); fs.writeFileSync(file + '.tmp', canonical(state) + '\n', { mode: 0o600 }); fs.renameSync(file + '.tmp', file); return clone(current); } finally { fs.rmdirSync(lock); this.#running.delete(actionId); } } }