fix(persona): gate runtime projection by repository binding
This commit is contained in:
parent
8412d0e458
commit
ebe12763f1
6 changed files with 111 additions and 15 deletions
|
|
@ -109,6 +109,9 @@ REVISE | REFUSE`。
|
||||||
- 人格语言唤醒绑定编译器源码为 `100`:只接受唯一且干净的人格根、已登记本机设备身份、与 manifest
|
- 人格语言唤醒绑定编译器源码为 `100`:只接受唯一且干净的人格根、已登记本机设备身份、与 manifest
|
||||||
精确一致的模型端点及唯一可激活只读事实器官,并外显冰朔责任、铸渊认知作者和本轮开发归因。
|
精确一致的模型端点及唯一可激活只读事实器官,并外显冰朔责任、铸渊认知作者和本轮开发归因。
|
||||||
它不创建人格仓库、不注册节点,也不把源码编译结果冒充桌面运行集成。
|
它不创建人格仓库、不注册节点,也不把源码编译结果冒充桌面运行集成。
|
||||||
|
- 人格运行投影的仓库前置门源码为 `100`:它必须先得到唯一证据绑定,才允许向原生只读运行查询传入
|
||||||
|
该一个规范仓库;未绑定和歧义状态直接保持原义,不再把普通挂载仓库的 manifest 缺失显示为人格运行时
|
||||||
|
回执故障。该门只收束事实读取范围,不创建、选择或唤醒人格仓库。
|
||||||
- 完整 HoloLake Runtime 与单 AGE 纵向闭环仍为 `0`:桌面语言入口绑定、真实人格仓库 manifest 绑定和
|
- 完整 HoloLake Runtime 与单 AGE 纵向闭环仍为 `0`:桌面语言入口绑定、真实人格仓库 manifest 绑定和
|
||||||
桌面运行验收尚未完成,因此不能用本轮源码测试冒充可用产品。
|
桌面运行验收尚未完成,因此不能用本轮源码测试冒充可用产品。
|
||||||
- Mirror runner、制品、部署和运行健康:`0`。
|
- Mirror runner、制品、部署和运行健康:`0`。
|
||||||
|
|
|
||||||
|
|
@ -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.
|
- **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.
|
- **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.
|
- **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`
|
- **Persona runtime projection** — `persona_runtime_projection_gated`, `persona_runtime_projection_loaded`,
|
||||||
record only coarse phase and count fields. Repository paths, persona session ids, event hashes, and runtime
|
and `persona_runtime_projection_retry` record only coarse phase and count fields. Repository paths, persona
|
||||||
attribution are never sent.
|
session ids, event hashes, and runtime attribution are never sent.
|
||||||
|
|
||||||
### Tauri Commands
|
### Tauri Commands
|
||||||
- **`reinit_telemetry`** — Re-reads settings and toggles Rust Sentry on/off. Called from frontend when user changes crash reporting setting.
|
- **`reinit_telemetry`** — Re-reads settings and toggles Rust Sentry on/off. Called from frontend when user changes crash reporting setting.
|
||||||
|
|
|
||||||
|
|
@ -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
|
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.
|
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
|
The runtime projection repeats the fail-closed discovery boundary and queries the native read model only
|
||||||
model. The renderer validates the receipt identity and required evidence, preserves partial query errors,
|
after exactly one persona repository is evidence-bound. It passes only that canonical repository path into
|
||||||
and displays the newest verified
|
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,
|
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
|
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.
|
proof that the persona is offline. This surface contains no wake, inference, lease, or execution control.
|
||||||
|
|
|
||||||
|
|
@ -2,19 +2,29 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { PersonaRuntimeProjectionPanel } from './PersonaRuntimeProjectionPanel'
|
import { PersonaRuntimeProjectionPanel } from './PersonaRuntimeProjectionPanel'
|
||||||
|
|
||||||
const { loadPersonaRuntimeProjectionMock, trackEventMock } = vi.hoisted(() => ({
|
const { loadPersonaRuntimeProjectionMock, resolvePersonaRepositoryBindingMock, trackEventMock } = vi.hoisted(() => ({
|
||||||
loadPersonaRuntimeProjectionMock: vi.fn(),
|
loadPersonaRuntimeProjectionMock: vi.fn(),
|
||||||
|
resolvePersonaRepositoryBindingMock: vi.fn(),
|
||||||
trackEventMock: vi.fn(),
|
trackEventMock: vi.fn(),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
vi.mock('../lib/personaRuntimeProjection', () => ({
|
vi.mock('../lib/personaRuntimeProjection', () => ({
|
||||||
loadPersonaRuntimeProjection: loadPersonaRuntimeProjectionMock,
|
loadPersonaRuntimeProjection: loadPersonaRuntimeProjectionMock,
|
||||||
}))
|
}))
|
||||||
|
vi.mock('../lib/personaRepositoryBinding', () => ({
|
||||||
|
resolvePersonaRepositoryBinding: resolvePersonaRepositoryBindingMock,
|
||||||
|
}))
|
||||||
vi.mock('../lib/telemetry', () => ({ trackEvent: trackEventMock }))
|
vi.mock('../lib/telemetry', () => ({ trackEvent: trackEventMock }))
|
||||||
|
|
||||||
describe('PersonaRuntimeProjectionPanel', () => {
|
describe('PersonaRuntimeProjectionPanel', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
resolvePersonaRepositoryBindingMock.mockResolvedValue({
|
||||||
|
phase: 'bound',
|
||||||
|
inspectedRepositoryCount: 1,
|
||||||
|
failures: [],
|
||||||
|
binding: { repositoryPath: '/repo/persona' },
|
||||||
|
})
|
||||||
loadPersonaRuntimeProjectionMock.mockResolvedValue({
|
loadPersonaRuntimeProjectionMock.mockResolvedValue({
|
||||||
errors: [],
|
errors: [],
|
||||||
inspectedRepositoryCount: 1,
|
inspectedRepositoryCount: 1,
|
||||||
|
|
@ -69,6 +79,10 @@ describe('PersonaRuntimeProjectionPanel', () => {
|
||||||
expect(screen.getByText('event-chain-verified')).toBeInTheDocument()
|
expect(screen.getByText('event-chain-verified')).toBeInTheDocument()
|
||||||
expect(screen.getByText(/guanghu\.zhuyuan-cognitive-gravity-frame\/v1/)).toBeInTheDocument()
|
expect(screen.getByText(/guanghu\.zhuyuan-cognitive-gravity-frame\/v1/)).toBeInTheDocument()
|
||||||
expect(screen.getByText('1111111111111111111111111111111111111111')).toBeInTheDocument()
|
expect(screen.getByText('1111111111111111111111111111111111111111')).toBeInTheDocument()
|
||||||
|
expect(loadPersonaRuntimeProjectionMock).toHaveBeenCalledWith({
|
||||||
|
personaId: 'ICE-P-ZY001',
|
||||||
|
repositoryPaths: ['/repo/persona'],
|
||||||
|
})
|
||||||
expect(trackEventMock).toHaveBeenCalledWith('persona_runtime_projection_loaded', {
|
expect(trackEventMock).toHaveBeenCalledWith('persona_runtime_projection_loaded', {
|
||||||
error_count: 0,
|
error_count: 0,
|
||||||
inspected_repository_count: 1,
|
inspected_repository_count: 1,
|
||||||
|
|
@ -125,4 +139,43 @@ describe('PersonaRuntimeProjectionPanel', () => {
|
||||||
await waitFor(() => expect(loadPersonaRuntimeProjectionMock).toHaveBeenCalledTimes(2))
|
await waitFor(() => expect(loadPersonaRuntimeProjectionMock).toHaveBeenCalledTimes(2))
|
||||||
expect(trackEventMock).toHaveBeenCalledWith('persona_runtime_projection_retry')
|
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(
|
||||||
|
<PersonaRuntimeProjectionPanel
|
||||||
|
locale="zh-CN"
|
||||||
|
personaId="ICE-P-ZY001"
|
||||||
|
repositoryPaths={['/ordinary']}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
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(
|
||||||
|
<PersonaRuntimeProjectionPanel
|
||||||
|
locale="zh-CN"
|
||||||
|
personaId="ICE-P-ZY001"
|
||||||
|
repositoryPaths={['/persona-a', '/persona-b']}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
await waitFor(() => expect(screen.getByText('发现多个有效仓库,拒绝自动选择')).toBeInTheDocument())
|
||||||
|
expect(loadPersonaRuntimeProjectionMock).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@ import {
|
||||||
type PersonaRuntimeProjection,
|
type PersonaRuntimeProjection,
|
||||||
type PersonaRuntimeSessionProjection,
|
type PersonaRuntimeSessionProjection,
|
||||||
} from '../lib/personaRuntimeProjection'
|
} from '../lib/personaRuntimeProjection'
|
||||||
|
import {
|
||||||
|
resolvePersonaRepositoryBinding,
|
||||||
|
type PersonaRepositoryBindingResolution,
|
||||||
|
} from '../lib/personaRepositoryBinding'
|
||||||
import { trackEvent } from '../lib/telemetry'
|
import { trackEvent } from '../lib/telemetry'
|
||||||
import { Button } from './ui/button'
|
import { Button } from './ui/button'
|
||||||
|
|
||||||
|
|
@ -14,7 +18,8 @@ type PersonaRuntimeProjectionPanelProps = {
|
||||||
repositoryPaths: readonly string[]
|
repositoryPaths: readonly string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type PanelState = PersonaRuntimeProjection | { phase: 'checking' }
|
type RepositoryGateState = Exclude<PersonaRepositoryBindingResolution, { phase: 'bound' }>
|
||||||
|
type PanelState = PersonaRuntimeProjection | RepositoryGateState | { phase: 'checking' }
|
||||||
|
|
||||||
function stateTranslationKey(state: string): TranslationKey {
|
function stateTranslationKey(state: string): TranslationKey {
|
||||||
if (state.startsWith('DORMANT')) return 'hololake.personaRuntime.state.dormant'
|
if (state.startsWith('DORMANT')) return 'hololake.personaRuntime.state.dormant'
|
||||||
|
|
@ -100,10 +105,25 @@ export function PersonaRuntimeProjectionPanel({
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let active = true
|
let active = true
|
||||||
void loadPersonaRuntimeProjection({
|
const load = async () => {
|
||||||
personaId,
|
const bindingResolution = await resolvePersonaRepositoryBinding({
|
||||||
repositoryPaths: repositorySignature ? repositorySignature.split('\u0000') : [],
|
personaId,
|
||||||
}).then((projection) => {
|
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
|
if (!active) return
|
||||||
setState(projection)
|
setState(projection)
|
||||||
trackEvent('persona_runtime_projection_loaded', {
|
trackEvent('persona_runtime_projection_loaded', {
|
||||||
|
|
@ -112,7 +132,8 @@ export function PersonaRuntimeProjectionPanel({
|
||||||
phase: projection.phase,
|
phase: projection.phase,
|
||||||
session_count: projection.sessions.length,
|
session_count: projection.sessions.length,
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
void load()
|
||||||
return () => {
|
return () => {
|
||||||
active = false
|
active = false
|
||||||
}
|
}
|
||||||
|
|
@ -143,10 +164,26 @@ export function PersonaRuntimeProjectionPanel({
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
{state.phase === 'unbound' ? (
|
||||||
|
<div className="persona-runtime-projection__message">
|
||||||
|
<strong>{translate(locale, 'hololake.personaRepository.unbound')}</strong>
|
||||||
|
<p>{translate(locale, 'hololake.personaRepository.unboundDescription')}</p>
|
||||||
|
<Button variant="outline" onClick={refresh}>{translate(locale, 'hololake.personaRuntime.retry')}</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{state.phase === 'ambiguous' ? (
|
||||||
|
<div className="persona-runtime-projection__message persona-runtime-projection__message--error" role="alert">
|
||||||
|
<strong>{translate(locale, 'hololake.personaRepository.ambiguous')}</strong>
|
||||||
|
<p>{translate(locale, 'hololake.personaRepository.ambiguousDescription')}</p>
|
||||||
|
<Button variant="outline" onClick={refresh}>{translate(locale, 'hololake.personaRuntime.retry')}</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{state.phase === 'error' ? (
|
{state.phase === 'error' ? (
|
||||||
<div className="persona-runtime-projection__message persona-runtime-projection__message--error" role="alert">
|
<div className="persona-runtime-projection__message persona-runtime-projection__message--error" role="alert">
|
||||||
<strong>{translate(locale, 'hololake.personaRuntime.error')}</strong>
|
<strong>{translate(locale, 'hololake.personaRuntime.error')}</strong>
|
||||||
<code>{state.errors[0]?.code}</code>
|
<code>{'code' in state ? state.code : state.errors[0]?.code}</code>
|
||||||
<Button variant="outline" onClick={refresh}>{translate(locale, 'hololake.personaRuntime.retry')}</Button>
|
<Button variant="outline" onClick={refresh}>{translate(locale, 'hololake.personaRuntime.retry')}</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"schema": "hololake.current-architecture/v1",
|
"schema": "hololake.current-architecture/v1",
|
||||||
"architecture_id": "HLP-CURRENT-ARCH-001",
|
"architecture_id": "HLP-CURRENT-ARCH-001",
|
||||||
"version": "2026-08-12.5",
|
"version": "2026-08-12.6",
|
||||||
"state": "CURRENT_CANONICAL",
|
"state": "CURRENT_CANONICAL",
|
||||||
"product": {
|
"product": {
|
||||||
"formal_name": "光湖语言系统 · 通用人工智能操作平台",
|
"formal_name": "光湖语言系统 · 通用人工智能操作平台",
|
||||||
|
|
@ -113,6 +113,7 @@
|
||||||
"persona_repository_binding_discovery_source_implemented": true,
|
"persona_repository_binding_discovery_source_implemented": true,
|
||||||
"persona_language_wake_binding_compiler_source_implemented": true,
|
"persona_language_wake_binding_compiler_source_implemented": true,
|
||||||
"persona_repository_binding_read_only_ui_source_integrated": true,
|
"persona_repository_binding_read_only_ui_source_integrated": true,
|
||||||
|
"persona_runtime_projection_repository_gate_source_integrated": true,
|
||||||
"real_persona_repository_manifest_bound": false,
|
"real_persona_repository_manifest_bound": false,
|
||||||
"natural_language_partner_adapter_runtime_integrated": false,
|
"natural_language_partner_adapter_runtime_integrated": false,
|
||||||
"required_natural_language_entry": "PARTNER_DELIBERATION_AND_LANGUAGE_HOME_INTEGRITY_GATE",
|
"required_natural_language_entry": "PARTNER_DELIBERATION_AND_LANGUAGE_HOME_INTEGRITY_GATE",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue