feat: project native persona authorization status

This commit is contained in:
冰朔 2026-08-12 06:29:51 +08:00
commit 8f18c3fba3
38 changed files with 268 additions and 40 deletions

View file

@ -43,6 +43,13 @@ describe('PersonaLanguageShellPanel', () => {
repositoryPaths={['/persona']}
providers={[provider]}
planGoal={planGoal}
loadAuthorizationStatus={async () => ({
phase: 'system_direct',
reason: 'NO_TRUSTED_SIGNER',
sourceCommit: 'a'.repeat(40),
signerCount: 0,
authorizationEnabled: false,
})}
/>,
)
@ -56,10 +63,12 @@ describe('PersonaLanguageShellPanel', () => {
repositoryPaths: ['/persona'],
providers: [provider],
developmentId: 'DEV-20260811-010',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.15',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.16',
}))
expect(await screen.findByText('我还不能执行:需要当前证据。')).toBeInTheDocument()
expect(screen.getByText('核验当前事实后规划下一步')).toBeInTheDocument()
expect(screen.getByText('系统直连 · 当前没有可信授权签名方')).toBeInTheDocument()
expect(screen.getByText('a'.repeat(40))).toBeInTheDocument()
})
it('shows the repository gate and never calls a later lifecycle itself', async () => {
@ -76,6 +85,13 @@ describe('PersonaLanguageShellPanel', () => {
repositoryPaths={['/ordinary']}
providers={[provider]}
planGoal={planGoal}
loadAuthorizationStatus={async () => ({
phase: 'unavailable',
reason: 'REGISTRY_UNAVAILABLE',
sourceCommit: null,
signerCount: 0,
authorizationEnabled: false,
})}
/>,
)
@ -84,5 +100,6 @@ describe('PersonaLanguageShellPanel', () => {
expect(await screen.findByText('这只说明当前挂载范围没有匹配的完整证据,不能据此判定人格不存在。')).toBeInTheDocument()
expect(screen.queryByLabelText('reality boundary confirmation')).not.toBeInTheDocument()
expect(screen.getByText('授权状态不可核验 · 保持系统直连')).toBeInTheDocument()
})
})

View file

@ -1,7 +1,11 @@
import { useMemo, useRef, useState } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import type { AiModelProvider } from '../lib/aiTargets'
import { DEFAULT_LANGUAGE_WORLD_UI_PLUGIN } from '../lib/defaultLanguageWorldUiPlugin'
import { translate, type AppLocale } from '../lib/i18n'
import {
loadPersonaControlAuthorizationStatus,
type PersonaControlAuthorizationStatus,
} from '../lib/personaControlAuthorization'
import {
planPersonaLanguageShellGoal,
type PersonaLanguageShellPlan,
@ -13,10 +17,11 @@ import {
type LanguageShellViewState,
} from './HotPluggableLanguageShell'
const CURRENT_ARCHITECTURE_ANCHOR = 'HLP-CURRENT-ARCH-001@2026-08-12.15'
const CURRENT_ARCHITECTURE_ANCHOR = 'HLP-CURRENT-ARCH-001@2026-08-12.16'
const DEVELOPMENT_ID = 'DEV-20260811-010'
type Planner = typeof planPersonaLanguageShellGoal
type AuthorizationStatusLoader = () => Promise<PersonaControlAuthorizationStatus>
type PersonaLanguageShellPanelProps = {
locale: AppLocale
@ -24,6 +29,7 @@ type PersonaLanguageShellPanelProps = {
repositoryPaths: readonly string[]
providers: readonly AiModelProvider[]
planGoal?: Planner
loadAuthorizationStatus?: AuthorizationStatusLoader
}
function requestId(): string {
@ -78,6 +84,7 @@ export function PersonaLanguageShellPanel({
repositoryPaths,
providers,
planGoal = planPersonaLanguageShellGoal,
loadAuthorizationStatus = loadPersonaControlAuthorizationStatus,
}: PersonaLanguageShellPanelProps) {
const plugin = useMemo(() => localizedPlugin(locale), [locale])
const requestSequence = useRef(0)
@ -87,6 +94,15 @@ export function PersonaLanguageShellPanel({
receipt: null,
evidence: null,
})
const [authorizationStatus, setAuthorizationStatus] = useState<PersonaControlAuthorizationStatus | null>(null)
useEffect(() => {
let current = true
void loadAuthorizationStatus().then((status) => {
if (current) setAuthorizationStatus(status)
})
return () => { current = false }
}, [loadAuthorizationStatus])
const submitGoal = (utterance: string) => {
if (!utterance.trim()) return
@ -146,6 +162,18 @@ export function PersonaLanguageShellPanel({
</div>
<small>{translate(locale, 'hololake.personaRuntime.readOnly')}</small>
</header>
<div className="persona-runtime-projection__message" aria-live="polite">
<p>
{authorizationStatus === null
? translate(locale, 'hololake.personaRuntime.checking')
: authorizationStatus.phase === 'system_direct'
? translate(locale, 'hololake.personaAuthorization.systemDirect')
: translate(locale, 'hololake.personaAuthorization.unavailable')}
</p>
{authorizationStatus?.phase === 'system_direct' && (
<code>{authorizationStatus.sourceCommit}</code>
)}
</div>
<HotPluggableLanguageShell
plugin={plugin}
state={state}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git 证据",
"hololake.personaRuntime.eventChain": "事件链头",
"hololake.personaRuntime.attribution": "人类责任主体",
"hololake.personaRuntime.readOnly": "只读投影 · 不触发执行动作"
"hololake.personaRuntime.readOnly": "只读投影 · 不触发执行动作",
"hololake.personaAuthorization.systemDirect": "系统直连 · 当前没有可信授权签名方",
"hololake.personaAuthorization.unavailable": "授权状态不可核验 · 保持系统直连"
}

View file

@ -1169,5 +1169,7 @@
"hololake.personaRuntime.gitHead": "Git evidence",
"hololake.personaRuntime.eventChain": "Event chain head",
"hololake.personaRuntime.attribution": "Human responsibility",
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action"
"hololake.personaRuntime.readOnly": "Read-only projection · no execution action",
"hololake.personaAuthorization.systemDirect": "System direct · no trusted authorization signer is registered",
"hololake.personaAuthorization.unavailable": "Authorization status unverifiable · remaining system direct"
}

View file

@ -39,6 +39,22 @@ export type NativePersonaControlAuthorizationVerification = {
authorizationEnabled: true
}
export type PersonaControlAuthorizationStatus =
| {
phase: 'system_direct'
reason: 'NO_TRUSTED_SIGNER'
sourceCommit: string
signerCount: 0
authorizationEnabled: false
}
| {
phase: 'unavailable'
reason: string
sourceCommit: null
signerCount: 0
authorizationEnabled: false
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
@ -62,3 +78,58 @@ export function hasPersonaPrimaryControlAuthorization(
&& typeof signerId === 'string'
&& /^[A-Z0-9][A-Z0-9._:@-]{1,159}$/.test(signerId)
}
/**
* Projects only the currently published empty-trust registry state. A future
* non-empty registry is not interpreted here because signer presence alone is
* never persona-control authorization.
*/
export function parsePersonaControlAuthorizationStatus(
receipt: unknown,
): PersonaControlAuthorizationStatus {
if (!isRecord(receipt) || Object.keys(receipt).length !== 5) {
return unavailableStatus('REGISTRY_RECEIPT_INVALID')
}
const sourceCommit = Reflect.get(receipt, 'sourceCommit')
if (
exactString(receipt, 'status', 'CURRENT_EMPTY')
&& Reflect.get(receipt, 'reason') === null
&& typeof sourceCommit === 'string'
&& /^[a-f0-9]{40}([a-f0-9]{24})?$/.test(sourceCommit)
&& Reflect.get(receipt, 'signerCount') === 0
&& Reflect.get(receipt, 'authorizationEnabled') === false
) {
return {
phase: 'system_direct',
reason: 'NO_TRUSTED_SIGNER',
sourceCommit,
signerCount: 0,
authorizationEnabled: false,
}
}
const reason = Reflect.get(receipt, 'reason')
return unavailableStatus(typeof reason === 'string' && reason ? reason : 'REGISTRY_RECEIPT_INVALID')
}
function unavailableStatus(reason: string): PersonaControlAuthorizationStatus {
return {
phase: 'unavailable',
reason,
sourceCommit: null,
signerCount: 0,
authorizationEnabled: false,
}
}
export async function loadPersonaControlAuthorizationStatus(): Promise<PersonaControlAuthorizationStatus> {
try {
const receipt = isTauri()
? await invoke<unknown>('load_persona_control_authorization_registry')
: await mockInvoke<unknown>('load_persona_control_authorization_registry')
return parsePersonaControlAuthorizationStatus(receipt)
} catch {
return unavailableStatus('REGISTRY_UNAVAILABLE')
}
}
import { invoke } from '@tauri-apps/api/core'
import { isTauri, mockInvoke } from '../mock-tauri'

View file

@ -0,0 +1,53 @@
import { describe, expect, it } from 'vitest'
import { parsePersonaControlAuthorizationStatus } from './personaControlAuthorization'
const currentEmpty = {
status: 'CURRENT_EMPTY',
reason: null,
sourceCommit: 'a'.repeat(40),
signerCount: 0,
authorizationEnabled: false,
}
describe('persona-control authorization status projection', () => {
it('projects only the exact native empty-trust receipt as system-direct', () => {
expect(parsePersonaControlAuthorizationStatus(currentEmpty)).toEqual({
phase: 'system_direct',
reason: 'NO_TRUSTED_SIGNER',
sourceCommit: 'a'.repeat(40),
signerCount: 0,
authorizationEnabled: false,
})
})
it.each([
['extra field', { ...currentEmpty, trusted: true }],
['non-empty registry', { ...currentEmpty, status: 'CURRENT', signerCount: 1 }],
['authorization enabled', { ...currentEmpty, authorizationEnabled: true }],
['invalid commit', { ...currentEmpty, sourceCommit: 'main' }],
])('fails closed for %s', (_label, receipt) => {
expect(parsePersonaControlAuthorizationStatus(receipt)).toEqual({
phase: 'unavailable',
reason: 'REGISTRY_RECEIPT_INVALID',
sourceCommit: null,
signerCount: 0,
authorizationEnabled: false,
})
})
it('preserves a bounded native unavailable reason without creating authority', () => {
expect(parsePersonaControlAuthorizationStatus({
status: 'UNAVAILABLE',
reason: 'ANCHOR_FETCH_FAILED',
sourceCommit: null,
signerCount: 0,
authorizationEnabled: false,
})).toEqual({
phase: 'unavailable',
reason: 'ANCHOR_FETCH_FAILED',
sourceCommit: null,
signerCount: 0,
authorizationEnabled: false,
})
})
})