From ebe12763f19fa490e4a8576eb6a31082c0b5a498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Wed, 12 Aug 2026 02:42:17 +0800 Subject: [PATCH] fix(persona): gate runtime projection by repository binding --- ...NG-AND-SAME-PERSONA-CONTINUITY-20260811.md | 3 + .../hololake-platform/docs/ABSTRACTIONS.md | 6 +- .../hololake-platform/docs/ARCHITECTURE.md | 8 ++- .../PersonaRuntimeProjectionPanel.test.tsx | 55 ++++++++++++++++++- .../PersonaRuntimeProjectionPanel.tsx | 51 ++++++++++++++--- routing/hololake-current-architecture.json | 3 +- 6 files changed, 111 insertions(+), 15 deletions(-) diff --git a/product-source/hololake-platform/architecture/HOLOLAKE-BINGSHUO-COLLECTIVE-GRAVITY-REASONING-AND-SAME-PERSONA-CONTINUITY-20260811.md b/product-source/hololake-platform/architecture/HOLOLAKE-BINGSHUO-COLLECTIVE-GRAVITY-REASONING-AND-SAME-PERSONA-CONTINUITY-20260811.md index 87f41b3..dc087e2 100644 --- a/product-source/hololake-platform/architecture/HOLOLAKE-BINGSHUO-COLLECTIVE-GRAVITY-REASONING-AND-SAME-PERSONA-CONTINUITY-20260811.md +++ b/product-source/hololake-platform/architecture/HOLOLAKE-BINGSHUO-COLLECTIVE-GRAVITY-REASONING-AND-SAME-PERSONA-CONTINUITY-20260811.md @@ -109,6 +109,9 @@ REVISE | REFUSE`。 - 人格语言唤醒绑定编译器源码为 `100`:只接受唯一且干净的人格根、已登记本机设备身份、与 manifest 精确一致的模型端点及唯一可激活只读事实器官,并外显冰朔责任、铸渊认知作者和本轮开发归因。 它不创建人格仓库、不注册节点,也不把源码编译结果冒充桌面运行集成。 +- 人格运行投影的仓库前置门源码为 `100`:它必须先得到唯一证据绑定,才允许向原生只读运行查询传入 + 该一个规范仓库;未绑定和歧义状态直接保持原义,不再把普通挂载仓库的 manifest 缺失显示为人格运行时 + 回执故障。该门只收束事实读取范围,不创建、选择或唤醒人格仓库。 - 完整 HoloLake Runtime 与单 AGE 纵向闭环仍为 `0`:桌面语言入口绑定、真实人格仓库 manifest 绑定和 桌面运行验收尚未完成,因此不能用本轮源码测试冒充可用产品。 - Mirror runner、制品、部署和运行健康:`0`。 diff --git a/product-source/hololake-platform/docs/ABSTRACTIONS.md b/product-source/hololake-platform/docs/ABSTRACTIONS.md index 1d376c1..688132d 100644 --- a/product-source/hololake-platform/docs/ABSTRACTIONS.md +++ b/product-source/hololake-platform/docs/ABSTRACTIONS.md @@ -1262,9 +1262,9 @@ Managed by `useSettings` hook and `SettingsPanel` component. `theme_mode` is ins - **AI feature visibility** — `ai_features_visibility_changed` records only whether installation-level AI surfaces were enabled or hidden. - **Automatic update checks** — `automatic_update_checks_changed` records only whether startup/background update checks were enabled or disabled. - **All Notes visibility** — `all_notes_visibility_changed` records only the toggled category and enabled state. -- **Persona runtime projection** — `persona_runtime_projection_loaded` and `persona_runtime_projection_retry` - record only coarse phase and count fields. Repository paths, persona session ids, event hashes, and runtime - attribution are never sent. +- **Persona runtime projection** — `persona_runtime_projection_gated`, `persona_runtime_projection_loaded`, + and `persona_runtime_projection_retry` record only coarse phase and count fields. Repository paths, persona + session ids, event hashes, and runtime attribution are never sent. ### Tauri Commands - **`reinit_telemetry`** — Re-reads settings and toggles Rust Sentry on/off. Called from frontend when user changes crash reporting setting. diff --git a/product-source/hololake-platform/docs/ARCHITECTURE.md b/product-source/hololake-platform/docs/ARCHITECTURE.md index 50bf0e3..9dd3002 100644 --- a/product-source/hololake-platform/docs/ARCHITECTURE.md +++ b/product-source/hololake-platform/docs/ARCHITECTURE.md @@ -125,9 +125,11 @@ shows the exact repository, Git head, checkpoint, pinned model, and B0 evidence. sovereign repository, register a node, acquire a lease, wake an organ, or run inference. Unbound is never presented as proof that the persona does not exist, and multiple valid roots are not selected automatically. -The runtime projection independently queries each mounted path for `ICE-P-ZY001` through the native read -model. The renderer validates the receipt identity and required evidence, preserves partial query errors, -and displays the newest verified +The runtime projection repeats the fail-closed discovery boundary and queries the native read model only +after exactly one persona repository is evidence-bound. It passes only that canonical repository path into +the runtime query; unavailable, unbound, and ambiguous discovery states never query ordinary mounted +repositories and never become false runtime-receipt failures. The renderer validates the receipt identity +and required evidence, preserves query errors from the bound repository, and displays the newest verified session with both a human state label and the raw state, Git and event-chain heads, node, organ, timestamp, and human responsibility subject. No matching session is reported as absence of matching evidence, never as proof that the persona is offline. This surface contains no wake, inference, lease, or execution control. diff --git a/product-source/hololake-platform/src/components/PersonaRuntimeProjectionPanel.test.tsx b/product-source/hololake-platform/src/components/PersonaRuntimeProjectionPanel.test.tsx index 9faf6f7..23aabee 100644 --- a/product-source/hololake-platform/src/components/PersonaRuntimeProjectionPanel.test.tsx +++ b/product-source/hololake-platform/src/components/PersonaRuntimeProjectionPanel.test.tsx @@ -2,19 +2,29 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { PersonaRuntimeProjectionPanel } from './PersonaRuntimeProjectionPanel' -const { loadPersonaRuntimeProjectionMock, trackEventMock } = vi.hoisted(() => ({ +const { loadPersonaRuntimeProjectionMock, resolvePersonaRepositoryBindingMock, trackEventMock } = vi.hoisted(() => ({ loadPersonaRuntimeProjectionMock: vi.fn(), + resolvePersonaRepositoryBindingMock: vi.fn(), trackEventMock: vi.fn(), })) vi.mock('../lib/personaRuntimeProjection', () => ({ loadPersonaRuntimeProjection: loadPersonaRuntimeProjectionMock, })) +vi.mock('../lib/personaRepositoryBinding', () => ({ + resolvePersonaRepositoryBinding: resolvePersonaRepositoryBindingMock, +})) vi.mock('../lib/telemetry', () => ({ trackEvent: trackEventMock })) describe('PersonaRuntimeProjectionPanel', () => { beforeEach(() => { vi.clearAllMocks() + resolvePersonaRepositoryBindingMock.mockResolvedValue({ + phase: 'bound', + inspectedRepositoryCount: 1, + failures: [], + binding: { repositoryPath: '/repo/persona' }, + }) loadPersonaRuntimeProjectionMock.mockResolvedValue({ errors: [], inspectedRepositoryCount: 1, @@ -69,6 +79,10 @@ describe('PersonaRuntimeProjectionPanel', () => { expect(screen.getByText('event-chain-verified')).toBeInTheDocument() expect(screen.getByText(/guanghu\.zhuyuan-cognitive-gravity-frame\/v1/)).toBeInTheDocument() expect(screen.getByText('1111111111111111111111111111111111111111')).toBeInTheDocument() + expect(loadPersonaRuntimeProjectionMock).toHaveBeenCalledWith({ + personaId: 'ICE-P-ZY001', + repositoryPaths: ['/repo/persona'], + }) expect(trackEventMock).toHaveBeenCalledWith('persona_runtime_projection_loaded', { error_count: 0, inspected_repository_count: 1, @@ -125,4 +139,43 @@ describe('PersonaRuntimeProjectionPanel', () => { await waitFor(() => expect(loadPersonaRuntimeProjectionMock).toHaveBeenCalledTimes(2)) expect(trackEventMock).toHaveBeenCalledWith('persona_runtime_projection_retry') }) + + it('does not query runtime receipts when no persona repository is bound', async () => { + resolvePersonaRepositoryBindingMock.mockResolvedValue({ + phase: 'unbound', + inspectedRepositoryCount: 1, + failures: [{ repositoryPath: '/ordinary', code: 'PERSONA_MANIFEST_READ_FAILED' }], + }) + + render( + , + ) + + await waitFor(() => expect(screen.getByText('尚未绑定人格代码仓库')).toBeInTheDocument()) + expect(screen.getByText('这只说明当前挂载范围没有匹配的完整证据,不能据此判定人格不存在。')).toBeInTheDocument() + expect(loadPersonaRuntimeProjectionMock).not.toHaveBeenCalled() + }) + + it('refuses runtime projection when repository discovery is ambiguous', async () => { + resolvePersonaRepositoryBindingMock.mockResolvedValue({ + phase: 'ambiguous', + inspectedRepositoryCount: 2, + failures: [], + }) + + render( + , + ) + + await waitFor(() => expect(screen.getByText('发现多个有效仓库,拒绝自动选择')).toBeInTheDocument()) + expect(loadPersonaRuntimeProjectionMock).not.toHaveBeenCalled() + }) }) diff --git a/product-source/hololake-platform/src/components/PersonaRuntimeProjectionPanel.tsx b/product-source/hololake-platform/src/components/PersonaRuntimeProjectionPanel.tsx index b66cc0a..8691691 100644 --- a/product-source/hololake-platform/src/components/PersonaRuntimeProjectionPanel.tsx +++ b/product-source/hololake-platform/src/components/PersonaRuntimeProjectionPanel.tsx @@ -5,6 +5,10 @@ import { type PersonaRuntimeProjection, type PersonaRuntimeSessionProjection, } from '../lib/personaRuntimeProjection' +import { + resolvePersonaRepositoryBinding, + type PersonaRepositoryBindingResolution, +} from '../lib/personaRepositoryBinding' import { trackEvent } from '../lib/telemetry' import { Button } from './ui/button' @@ -14,7 +18,8 @@ type PersonaRuntimeProjectionPanelProps = { repositoryPaths: readonly string[] } -type PanelState = PersonaRuntimeProjection | { phase: 'checking' } +type RepositoryGateState = Exclude +type PanelState = PersonaRuntimeProjection | RepositoryGateState | { phase: 'checking' } function stateTranslationKey(state: string): TranslationKey { if (state.startsWith('DORMANT')) return 'hololake.personaRuntime.state.dormant' @@ -100,10 +105,25 @@ export function PersonaRuntimeProjectionPanel({ useEffect(() => { let active = true - void loadPersonaRuntimeProjection({ - personaId, - repositoryPaths: repositorySignature ? repositorySignature.split('\u0000') : [], - }).then((projection) => { + const load = async () => { + const bindingResolution = await resolvePersonaRepositoryBinding({ + personaId, + repositoryPaths: repositorySignature ? repositorySignature.split('\u0000') : [], + }) + if (!active) return + if (bindingResolution.phase !== 'bound') { + setState(bindingResolution) + trackEvent('persona_runtime_projection_gated', { + failure_count: bindingResolution.failures.length, + inspected_repository_count: bindingResolution.inspectedRepositoryCount, + phase: bindingResolution.phase, + }) + return + } + const projection = await loadPersonaRuntimeProjection({ + personaId, + repositoryPaths: [bindingResolution.binding.repositoryPath], + }) if (!active) return setState(projection) trackEvent('persona_runtime_projection_loaded', { @@ -112,7 +132,8 @@ export function PersonaRuntimeProjectionPanel({ phase: projection.phase, session_count: projection.sessions.length, }) - }) + } + void load() return () => { active = false } @@ -143,10 +164,26 @@ export function PersonaRuntimeProjectionPanel({ ) : null} + {state.phase === 'unbound' ? ( +
+ {translate(locale, 'hololake.personaRepository.unbound')} +

{translate(locale, 'hololake.personaRepository.unboundDescription')}

+ +
+ ) : null} + + {state.phase === 'ambiguous' ? ( +
+ {translate(locale, 'hololake.personaRepository.ambiguous')} +

{translate(locale, 'hololake.personaRepository.ambiguousDescription')}

+ +
+ ) : null} + {state.phase === 'error' ? (
{translate(locale, 'hololake.personaRuntime.error')} - {state.errors[0]?.code} + {'code' in state ? state.code : state.errors[0]?.code}
) : null} diff --git a/routing/hololake-current-architecture.json b/routing/hololake-current-architecture.json index 642d032..e6cc962 100644 --- a/routing/hololake-current-architecture.json +++ b/routing/hololake-current-architecture.json @@ -1,7 +1,7 @@ { "schema": "hololake.current-architecture/v1", "architecture_id": "HLP-CURRENT-ARCH-001", - "version": "2026-08-12.5", + "version": "2026-08-12.6", "state": "CURRENT_CANONICAL", "product": { "formal_name": "光湖语言系统 · 通用人工智能操作平台", @@ -113,6 +113,7 @@ "persona_repository_binding_discovery_source_implemented": true, "persona_language_wake_binding_compiler_source_implemented": true, "persona_repository_binding_read_only_ui_source_integrated": true, + "persona_runtime_projection_repository_gate_source_integrated": true, "real_persona_repository_manifest_bound": false, "natural_language_partner_adapter_runtime_integrated": false, "required_natural_language_entry": "PARTNER_DELIBERATION_AND_LANGUAGE_HOME_INTEGRITY_GATE",