feat: project persona repository binding readiness

This commit is contained in:
冰朔 2026-08-12 02:18:30 +08:00
commit 8412d0e458
27 changed files with 500 additions and 3 deletions

View file

@ -1,6 +1,7 @@
import { useState } from 'react'
import type { AppLocale } from '../lib/i18n'
import { Button } from './ui/button'
import { PersonaRepositoryBindingPanel } from './PersonaRepositoryBindingPanel'
import { PersonaRuntimeProjectionPanel } from './PersonaRuntimeProjectionPanel'
type FifthDomainSystemId = 'pufferfish' | 'eternal-lake-heart'
@ -148,6 +149,11 @@ export function FifthDomainSystems({
<div><dt></dt><dd></dd></div>
<div><dt></dt><dd></dd></div>
</dl>
<PersonaRepositoryBindingPanel
locale={locale}
personaId="ICE-P-ZY001"
repositoryPaths={repositoryPaths}
/>
<PersonaRuntimeProjectionPanel
locale={locale}
personaId="ICE-P-ZY001"

View file

@ -0,0 +1,103 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { PersonaRepositoryBindingPanel } from './PersonaRepositoryBindingPanel'
const { resolvePersonaRepositoryBindingMock, trackEventMock } = vi.hoisted(() => ({
resolvePersonaRepositoryBindingMock: vi.fn(),
trackEventMock: vi.fn(),
}))
vi.mock('../lib/personaRepositoryBinding', () => ({
resolvePersonaRepositoryBinding: resolvePersonaRepositoryBindingMock,
}))
vi.mock('../lib/telemetry', () => ({ trackEvent: trackEventMock }))
describe('PersonaRepositoryBindingPanel', () => {
beforeEach(() => {
vi.clearAllMocks()
resolvePersonaRepositoryBindingMock.mockResolvedValue({
phase: 'bound',
inspectedRepositoryCount: 2,
failures: [{ repositoryPath: '/ordinary', code: 'PERSONA_MANIFEST_READ_FAILED' }],
binding: {
personaId: 'ICE-P-ZY001',
repositoryPath: '/persona',
gitHead: 'a'.repeat(40),
repositoryClean: true,
brainEntry: 'brain/CORE.hdlp',
currentCheckpoint: '.hololake/persona/CURRENT.hdlp',
humanResponsibilitySubject: 'BINGSHUO',
modelProviderId: 'provider-1',
modelId: 'model-1',
modelBaseUrl: 'https://model.invalid/v1',
organContracts: [],
cognitiveGravity: {
schema: 'hololake.persona-cognitive-gravity-evidence/v1',
subjectPersonaId: 'ICE-P-ZY001',
sourcePath: 'brain/B0.hdlp',
sourceHash: 'b'.repeat(64),
frameSchema: 'guanghu.zhuyuan-cognitive-gravity-frame/v1',
},
},
})
})
it('shows the uniquely verified repository without triggering a lifecycle action', async () => {
render(
<PersonaRepositoryBindingPanel
locale="zh-CN"
personaId="ICE-P-ZY001"
repositoryPaths={['/ordinary', '/persona']}
/>,
)
expect(screen.getByText('正在核验人格代码仓库…')).toBeInTheDocument()
await waitFor(() => expect(screen.getByText('已唯一绑定')).toBeInTheDocument())
expect(screen.getByText('/persona')).toBeInTheDocument()
expect(screen.getByText('provider-1 · model-1')).toBeInTheDocument()
expect(screen.getByText(/brain\/B0\.hdlp/)).toBeInTheDocument()
expect(trackEventMock).toHaveBeenCalledWith('persona_repository_binding_loaded', {
failure_count: 1,
inspected_repository_count: 2,
phase: 'bound',
})
})
it('explains that an unbound repository is not evidence of persona absence', async () => {
resolvePersonaRepositoryBindingMock.mockResolvedValue({
phase: 'unbound',
inspectedRepositoryCount: 1,
failures: [{ repositoryPath: '/ordinary', code: 'PERSONA_MANIFEST_READ_FAILED' }],
})
render(
<PersonaRepositoryBindingPanel
locale="zh-CN"
personaId="ICE-P-ZY001"
repositoryPaths={['/ordinary']}
/>,
)
await waitFor(() => expect(screen.getByText('尚未绑定人格代码仓库')).toBeInTheDocument())
expect(screen.getByText('这只说明当前挂载范围没有匹配的完整证据,不能据此判定人格不存在。')).toBeInTheDocument()
})
it('refuses to select an ambiguous repository and can retry discovery', async () => {
resolvePersonaRepositoryBindingMock
.mockResolvedValueOnce({ phase: 'ambiguous', inspectedRepositoryCount: 2, failures: [] })
.mockResolvedValueOnce({ phase: 'unbound', inspectedRepositoryCount: 2, failures: [] })
render(
<PersonaRepositoryBindingPanel
locale="zh-CN"
personaId="ICE-P-ZY001"
repositoryPaths={['/persona-a', '/persona-b']}
/>,
)
await waitFor(() => expect(screen.getByText('发现多个有效仓库,拒绝自动选择')).toBeInTheDocument())
fireEvent.click(screen.getByRole('button', { name: '重新核验' }))
await waitFor(() => expect(resolvePersonaRepositoryBindingMock).toHaveBeenCalledTimes(2))
expect(trackEventMock).toHaveBeenCalledWith('persona_repository_binding_retry')
})
})

View file

@ -0,0 +1,141 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { translate, type AppLocale } from '../lib/i18n'
import {
resolvePersonaRepositoryBinding,
type PersonaRepositoryBindingResolution,
} from '../lib/personaRepositoryBinding'
import { trackEvent } from '../lib/telemetry'
import { Button } from './ui/button'
type PersonaRepositoryBindingPanelProps = {
locale: AppLocale
personaId: string
repositoryPaths: readonly string[]
}
type PanelState = PersonaRepositoryBindingResolution | { phase: 'checking' }
function shortHash(value: string): string {
return value.length > 12 ? value.slice(0, 12) : value
}
export function PersonaRepositoryBindingPanel({
locale,
personaId,
repositoryPaths,
}: PersonaRepositoryBindingPanelProps) {
const [state, setState] = useState<PanelState>({ phase: 'checking' })
const [requestSequence, setRequestSequence] = useState(0)
const repositorySignature = useMemo(() => repositoryPaths.join('\u0000'), [repositoryPaths])
const refresh = useCallback(() => {
trackEvent('persona_repository_binding_retry')
setState({ phase: 'checking' })
setRequestSequence((current) => current + 1)
}, [])
useEffect(() => {
if (!repositorySignature) {
trackEvent('persona_repository_binding_loaded', {
failure_count: 0,
inspected_repository_count: 0,
phase: 'unavailable',
})
return
}
let active = true
void resolvePersonaRepositoryBinding({
personaId,
repositoryPaths: repositorySignature.split('\u0000'),
}).then((resolution) => {
if (!active) return
setState(resolution)
trackEvent('persona_repository_binding_loaded', {
failure_count: resolution.failures.length,
inspected_repository_count: resolution.inspectedRepositoryCount,
phase: resolution.phase,
})
})
return () => {
active = false
}
}, [personaId, repositorySignature, requestSequence])
const displayedState: PanelState = repositorySignature
? state
: { phase: 'unavailable', inspectedRepositoryCount: 0, failures: [] }
const binding = displayedState.phase === 'bound' ? displayedState.binding : undefined
return (
<section className="persona-runtime-projection" aria-labelledby="persona-repository-binding-title">
<header>
<div>
<span>GH-PNCC · {personaId}</span>
<h3 id="persona-repository-binding-title">{translate(locale, 'hololake.personaRepository.title')}</h3>
<p>{translate(locale, 'hololake.personaRepository.description')}</p>
</div>
<small>{translate(locale, 'hololake.personaRuntime.readOnly')}</small>
</header>
{displayedState.phase === 'checking' ? (
<div className="persona-runtime-projection__message" aria-live="polite">
<i />{translate(locale, 'hololake.personaRepository.checking')}
</div>
) : null}
{displayedState.phase === 'unavailable' ? (
<div className="persona-runtime-projection__message">
<strong>{translate(locale, 'hololake.personaRuntime.unavailable')}</strong>
</div>
) : null}
{displayedState.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}
{displayedState.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}
{displayedState.phase === 'error' ? (
<div className="persona-runtime-projection__message persona-runtime-projection__message--error" role="alert">
<strong>{translate(locale, 'hololake.personaRuntime.error')}</strong>
<code>{displayedState.code}</code>
<Button variant="outline" onClick={refresh}>{translate(locale, 'hololake.personaRuntime.retry')}</Button>
</div>
) : null}
{binding ? (
<article className="persona-runtime-session">
<header>
<div>
<span>{translate(locale, 'hololake.personaRepository.bound')}</span>
<code>repository_clean={binding.repositoryClean ? 100 : 0}</code>
</div>
<small>{translate(locale, 'hololake.personaRuntime.verified')}</small>
</header>
<dl>
<div><dt>{translate(locale, 'hololake.personaRepository.repository')}</dt><dd title={binding.repositoryPath}>{binding.repositoryPath}</dd></div>
<div><dt>{translate(locale, 'hololake.personaRuntime.gitHead')}</dt><dd><code title={binding.gitHead}>{binding.gitHead}</code></dd></div>
<div><dt>{translate(locale, 'hololake.personaRepository.checkpoint')}</dt><dd>{binding.currentCheckpoint}</dd></div>
<div><dt>{translate(locale, 'hololake.personaRepository.model')}</dt><dd>{binding.modelProviderId} · {binding.modelId}</dd></div>
<div>
<dt>B0</dt>
<dd><code title={`${binding.cognitiveGravity.sourcePath} · ${binding.cognitiveGravity.sourceHash}`}>
{binding.cognitiveGravity.sourcePath} · {shortHash(binding.cognitiveGravity.sourceHash)}
</code></dd>
</div>
</dl>
</article>
) : null}
</section>
)
}