fix: require native persona authorization verification

This commit is contained in:
冰朔 2026-08-12 06:10:49 +08:00
commit 584f38b478
13 changed files with 195 additions and 173 deletions

View file

@ -56,7 +56,7 @@ describe('PersonaLanguageShellPanel', () => {
repositoryPaths: ['/persona'],
providers: [provider],
developmentId: 'DEV-20260811-010',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.14',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.15',
}))
expect(await screen.findByText('我还不能执行:需要当前证据。')).toBeInTheDocument()
expect(screen.getByText('核验当前事实后规划下一步')).toBeInTheDocument()

View file

@ -13,7 +13,7 @@ import {
type LanguageShellViewState,
} from './HotPluggableLanguageShell'
const CURRENT_ARCHITECTURE_ANCHOR = 'HLP-CURRENT-ARCH-001@2026-08-12.14'
const CURRENT_ARCHITECTURE_ANCHOR = 'HLP-CURRENT-ARCH-001@2026-08-12.15'
const DEVELOPMENT_ID = 'DEV-20260811-010'
type Planner = typeof planPersonaLanguageShellGoal
@ -103,9 +103,9 @@ export function PersonaLanguageShellPanel({
personaId,
repositoryPaths,
providers,
// Native registry loading and signed-receipt verification exist, but
// no verified projection is wired into this controller. The published
// registry is also empty, so this remains system-direct.
// The controller can verify a candidate receipt through the native
// boundary, but no candidate source is mounted here and the published
// signer registry is empty. This therefore remains system-direct.
requestId: requestId(),
modelInstanceId: `hololake-desktop:${personaId}`,
developmentId: DEVELOPMENT_ID,

View file

@ -1,59 +1,31 @@
import { describe, expect, it } from 'vitest'
import {
PERSONA_CONTROL_AUTHORIZATION_SCHEMA,
PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE,
hasPersonaPrimaryControlAuthorization,
} from './personaControlAuthorization'
import { hasPersonaPrimaryControlAuthorization } from './personaControlAuthorization'
const expected = {
personaId: 'ICE-P-ZY001',
humanResponsibilitySubject: 'BINGSHUO',
repositoryHead: 'a'.repeat(40),
modelInstanceId: 'MODEL-INSTANCE-001',
requestId: 'REQ-001',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.14',
observedAt: Date.parse('2026-08-12T04:35:00+08:00'),
}
const receipt = {
schema: PERSONA_CONTROL_AUTHORIZATION_SCHEMA,
outcome: 'VERIFIED',
authorizationId: 'AUTH-001',
verifier: 'GUANGHU_OS',
scope: PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE,
personaId: expected.personaId,
humanResponsibilitySubject: expected.humanResponsibilitySubject,
repositoryHead: expected.repositoryHead,
modelInstanceId: expected.modelInstanceId,
requestId: expected.requestId,
sourceLanguageAnchor: expected.sourceLanguageAnchor,
issuedAt: '2026-08-12T04:30:00+08:00',
validUntil: '2026-08-12T04:40:00+08:00',
evidenceDigest: 'b'.repeat(64),
const verification = {
status: 'VERIFIED',
reason: null,
sourceCommit: 'a'.repeat(40),
signerId: 'GH-AIOS-AUTHORIZER-001',
signatureAlgorithm: 'Ed25519',
signature: 'A'.repeat(86),
authorizationEnabled: true,
}
describe('hasPersonaPrimaryControlAuthorization', () => {
it('accepts only an exact, current, Guanghu OS verified planning receipt', () => {
expect(hasPersonaPrimaryControlAuthorization(receipt, expected)).toBe(true)
it('accepts only an exact native cryptographic verification projection', () => {
expect(hasPersonaPrimaryControlAuthorization(verification)).toBe(true)
})
it.each([
['missing', null],
['wrong persona', { ...receipt, personaId: 'OTHER' }],
['wrong repository head', { ...receipt, repositoryHead: 'c'.repeat(40) }],
['wrong model instance', { ...receipt, modelInstanceId: 'OTHER' }],
['wrong request', { ...receipt, requestId: 'OTHER' }],
['wrong language anchor', { ...receipt, sourceLanguageAnchor: 'old' }],
['unverified', { ...receipt, outcome: 'PENDING' }],
['expired', { ...receipt, validUntil: '2026-08-12T04:34:59+08:00' }],
['future', { ...receipt, issuedAt: '2026-08-12T04:35:01+08:00' }],
['invalid digest', { ...receipt, evidenceDigest: 'not-a-digest' }],
['missing signer', { ...receipt, signerId: '' }],
['invalid signature', { ...receipt, signature: 'not-a-signature' }],
['self-claimed signed receipt', { schema: 'hololake.persona-control-authorization/v2', outcome: 'VERIFIED' }],
['native denial', { ...verification, status: 'DENIED', authorizationEnabled: false }],
['disabled projection', { ...verification, authorizationEnabled: false }],
['reason present', { ...verification, reason: 'NO_TRUSTED_SIGNER' }],
['missing signer', { ...verification, signerId: null }],
['invalid signer', { ...verification, signerId: 'lowercase' }],
['missing commit', { ...verification, sourceCommit: null }],
['invalid commit', { ...verification, sourceCommit: 'not-a-commit' }],
['unknown extra field', { ...verification, extra: true }],
])('fails closed for %s evidence', (_label, candidate) => {
expect(hasPersonaPrimaryControlAuthorization(candidate, expected)).toBe(false)
expect(hasPersonaPrimaryControlAuthorization(candidate)).toBe(false)
})
})

View file

@ -31,6 +31,14 @@ export type PersonaControlAuthorizationExpectation = {
observedAt?: number
}
export type NativePersonaControlAuthorizationVerification = {
status: 'VERIFIED'
reason: null
sourceCommit: string
signerId: string
authorizationEnabled: true
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
@ -39,50 +47,18 @@ function exactString(record: Record<string, unknown>, key: string, expected: str
return Reflect.get(record, key) === expected
}
/**
* Accepts only a native-verifier receipt bound to this exact planning request.
* It does not issue authority and treats absent or stale evidence as system-direct.
*/
/** Accepts only the bounded result emitted after native cryptographic verification. */
export function hasPersonaPrimaryControlAuthorization(
receipt: unknown,
expected: PersonaControlAuthorizationExpectation,
): receipt is PersonaControlAuthorizationReceipt {
if (!isRecord(receipt)) return false
if (!exactString(receipt, 'schema', PERSONA_CONTROL_AUTHORIZATION_SCHEMA)
|| !exactString(receipt, 'outcome', 'VERIFIED')
|| !exactString(receipt, 'verifier', 'GUANGHU_OS')
|| !exactString(receipt, 'signatureAlgorithm', 'Ed25519')
|| !exactString(receipt, 'scope', PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE)
|| !exactString(receipt, 'personaId', expected.personaId)
|| !exactString(receipt, 'humanResponsibilitySubject', expected.humanResponsibilitySubject)
|| !exactString(receipt, 'repositoryHead', expected.repositoryHead)
|| !exactString(receipt, 'modelInstanceId', expected.modelInstanceId)
|| !exactString(receipt, 'requestId', expected.requestId)
|| !exactString(receipt, 'sourceLanguageAnchor', expected.sourceLanguageAnchor)) {
return false
}
const authorizationId = Reflect.get(receipt, 'authorizationId')
const evidenceDigest = Reflect.get(receipt, 'evidenceDigest')
const signerId = Reflect.get(receipt, 'signerId')
const signature = Reflect.get(receipt, 'signature')
const issuedAt = Reflect.get(receipt, 'issuedAt')
const validUntil = Reflect.get(receipt, 'validUntil')
if (typeof authorizationId !== 'string' || !authorizationId.trim()
|| typeof evidenceDigest !== 'string' || !/^[a-f0-9]{64}$/.test(evidenceDigest)
|| typeof signerId !== 'string' || !/^[A-Z0-9][A-Z0-9._:@-]{1,159}$/.test(signerId)
|| typeof signature !== 'string' || !/^[A-Za-z0-9_-]{80,128}$/.test(signature)
|| typeof issuedAt !== 'string'
|| typeof validUntil !== 'string') {
return false
}
const issuedAtMs = Date.parse(issuedAt)
const validUntilMs = Date.parse(validUntil)
const observedAt = expected.observedAt ?? Date.now()
return Number.isFinite(issuedAtMs)
&& Number.isFinite(validUntilMs)
&& issuedAtMs <= observedAt
&& observedAt <= validUntilMs
&& issuedAtMs < validUntilMs
verification: unknown,
): verification is NativePersonaControlAuthorizationVerification {
if (!isRecord(verification) || Object.keys(verification).length !== 5) return false
const sourceCommit = Reflect.get(verification, 'sourceCommit')
const signerId = Reflect.get(verification, 'signerId')
return exactString(verification, 'status', 'VERIFIED')
&& Reflect.get(verification, 'reason') === null
&& Reflect.get(verification, 'authorizationEnabled') === true
&& typeof sourceCommit === 'string'
&& /^[a-f0-9]{40}([a-f0-9]{24})?$/.test(sourceCommit)
&& typeof signerId === 'string'
&& /^[A-Z0-9][A-Z0-9._:@-]{1,159}$/.test(signerId)
}

View file

@ -1,4 +1,4 @@
import { describe, expect, it, vi } from 'vitest'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { AiModelProvider } from './aiTargets'
import type { LanguageGoalProjection } from './languageOperatingModel'
import type { PersonaLanguageGoalBinding } from './personaLanguageGoal'
@ -8,6 +8,11 @@ import type {
} from './personaRepositoryBinding'
import { planPersonaLanguageShellGoal } from './personaLanguageShellController'
const native = vi.hoisted(() => ({ invoke: vi.fn() }))
vi.mock('@tauri-apps/api/core', () => ({ invoke: native.invoke }))
vi.mock('../mock-tauri', () => ({ isTauri: () => false, mockInvoke: native.invoke }))
const provider: AiModelProvider = {
id: 'ollama-local',
name: 'Local Ollama',
@ -104,6 +109,8 @@ function input() {
}
describe('planPersonaLanguageShellGoal', () => {
beforeEach(() => native.invoke.mockReset())
it.each(['unavailable', 'unbound', 'ambiguous', 'error'] as const)(
'fails closed at repository phase %s before device identity or cognition',
async (phase) => {
@ -175,30 +182,24 @@ describe('planPersonaLanguageShellGoal', () => {
expect(planGoal).toHaveBeenCalledWith(expect.objectContaining({ personaAuthorized: false }))
})
it('enables persona-primary planning only for an exact request-bound authorization receipt', async () => {
it('enables persona-primary planning only after the native verifier accepts the exact receipt', async () => {
const planGoal = vi.fn().mockResolvedValue(plannedGoal)
const candidateReceipt = {
schema: 'hololake.persona-control-authorization/v2',
outcome: 'VERIFIED',
authorizationId: 'AUTH-001',
}
native.invoke.mockResolvedValue({
status: 'VERIFIED',
reason: null,
sourceCommit: 'c'.repeat(40),
signerId: 'GH-AIOS-AUTHORIZER-001',
authorizationEnabled: true,
})
await planPersonaLanguageShellGoal({
...input(),
loadPersonaControlAuthorization: vi.fn().mockResolvedValue({
schema: 'hololake.persona-control-authorization/v2',
outcome: 'VERIFIED',
authorizationId: 'AUTH-001',
verifier: 'GUANGHU_OS',
scope: 'PERSONA_PRIMARY_LANGUAGE_PLANNING',
personaId: repositoryBinding.personaId,
humanResponsibilitySubject: repositoryBinding.humanResponsibilitySubject,
repositoryHead: repositoryBinding.gitHead,
modelInstanceId: 'MODEL-INSTANCE-001',
requestId: 'REQ-001',
sourceLanguageAnchor: 'current-human-utterance',
issuedAt: '2026-08-12T04:30:00+08:00',
validUntil: '2026-08-12T04:40:00+08:00',
evidenceDigest: 'c'.repeat(64),
signerId: 'GH-AIOS-AUTHORIZER-001',
signatureAlgorithm: 'Ed25519',
signature: 'A'.repeat(86),
}),
personaControlAuthorizationReceipt: candidateReceipt,
authorizationObservedAt: Date.parse('2026-08-12T04:35:00+08:00'),
resolveRepository: vi.fn().mockResolvedValue(boundResolution()),
loadDeviceIdentity: vi.fn().mockResolvedValue({
@ -209,9 +210,68 @@ describe('planPersonaLanguageShellGoal', () => {
planGoal,
})
expect(native.invoke).toHaveBeenCalledWith(
'verify_persona_control_authorization',
{
receipt: candidateReceipt,
expectation: {
personaId: repositoryBinding.personaId,
humanResponsibilitySubject: repositoryBinding.humanResponsibilitySubject,
repositoryHead: repositoryBinding.gitHead,
modelInstanceId: 'MODEL-INSTANCE-001',
requestId: 'REQ-001',
sourceLanguageAnchor: 'current-human-utterance',
observedAtMilliseconds: Date.parse('2026-08-12T04:35:00+08:00'),
},
},
)
expect(planGoal).toHaveBeenCalledWith(expect.objectContaining({ personaAuthorized: true }))
})
it('keeps system-direct planning for a native denial', async () => {
const planGoal = vi.fn().mockResolvedValue(plannedGoal)
native.invoke.mockResolvedValue({
status: 'DENIED',
reason: 'NO_TRUSTED_SIGNER',
sourceCommit: 'c'.repeat(40),
signerId: null,
authorizationEnabled: false,
})
await planPersonaLanguageShellGoal({
...input(),
personaControlAuthorizationReceipt: { outcome: 'VERIFIED' },
resolveRepository: vi.fn().mockResolvedValue(boundResolution()),
loadDeviceIdentity: vi.fn().mockResolvedValue({
schema: 'guanghu.router-device/v1',
device_id: 'HL-DEVICE-001',
public_key: 'public-key',
}),
planGoal,
})
expect(planGoal).toHaveBeenCalledWith(expect.objectContaining({ personaAuthorized: false }))
})
it('keeps system-direct planning when the native verifier is unavailable', async () => {
const planGoal = vi.fn().mockResolvedValue(plannedGoal)
native.invoke.mockImplementationOnce(() => { throw new Error('SOURCE_UNAVAILABLE') })
await planPersonaLanguageShellGoal({
...input(),
personaControlAuthorizationReceipt: { outcome: 'VERIFIED' },
resolveRepository: vi.fn().mockResolvedValue(boundResolution()),
loadDeviceIdentity: vi.fn().mockResolvedValue({
schema: 'guanghu.router-device/v1',
device_id: 'HL-DEVICE-001',
public_key: 'public-key',
}),
planGoal,
})
expect(planGoal).toHaveBeenCalledWith(expect.objectContaining({ personaAuthorized: false }))
})
it('returns a stable binding error and never invokes cognition for invalid device evidence', async () => {
const planGoal = vi.fn()

View file

@ -35,14 +35,18 @@ export type PersonaLanguageShellPlan =
type RepositoryResolver = typeof resolvePersonaRepositoryBinding
type GoalPlanner = (input: PlanPersonaLanguageGoalInput) => Promise<LanguageGoalProjection>
type DeviceIdentityLoader = () => Promise<unknown>
type PersonaControlAuthorizationLoader = (input: {
personaId: string
humanResponsibilitySubject: string
repositoryHead: string
modelInstanceId: string
requestId: string
sourceLanguageAnchor: string
}) => Promise<unknown>
type PersonaControlAuthorizationVerificationInput = {
receipt: unknown
expectation: {
personaId: string
humanResponsibilitySubject: string
repositoryHead: string
modelInstanceId: string
requestId: string
sourceLanguageAnchor: string
observedAtMilliseconds: number
}
}
async function loadNativeDeviceIdentity(): Promise<unknown> {
return isTauri()
@ -50,8 +54,12 @@ async function loadNativeDeviceIdentity(): Promise<unknown> {
: mockInvoke<unknown>('guanghu_router_device_identity')
}
async function loadNoPersonaControlAuthorization(): Promise<null> {
return null
async function verifyNativePersonaControlAuthorization(
input: PersonaControlAuthorizationVerificationInput,
): Promise<unknown> {
return isTauri()
? invoke<unknown>('verify_persona_control_authorization', input)
: mockInvoke<unknown>('verify_persona_control_authorization', input)
}
function errorCode(error: unknown, fallback: string): string {
@ -79,6 +87,7 @@ export async function planPersonaLanguageShellGoal({
personaId,
repositoryPaths,
providers,
personaControlAuthorizationReceipt,
authorizationObservedAt,
detectedBoundaries,
requestId,
@ -87,13 +96,13 @@ export async function planPersonaLanguageShellGoal({
sourceLanguageAnchor,
resolveRepository = resolvePersonaRepositoryBinding,
loadDeviceIdentity = loadNativeDeviceIdentity,
loadPersonaControlAuthorization = loadNoPersonaControlAuthorization,
planGoal = planPersonaLanguageGoal,
}: {
utterance: string
personaId: string
repositoryPaths: readonly string[]
providers: readonly AiModelProvider[]
personaControlAuthorizationReceipt?: unknown
authorizationObservedAt?: number
detectedBoundaries?: RealityBoundary[]
requestId: string
@ -102,7 +111,6 @@ export async function planPersonaLanguageShellGoal({
sourceLanguageAnchor: string
resolveRepository?: RepositoryResolver
loadDeviceIdentity?: DeviceIdentityLoader
loadPersonaControlAuthorization?: PersonaControlAuthorizationLoader
planGoal?: GoalPlanner
}): Promise<PersonaLanguageShellPlan> {
const repository = await resolveRepository({ personaId, repositoryPaths })
@ -144,20 +152,22 @@ export async function planPersonaLanguageShellGoal({
requestId,
sourceLanguageAnchor,
}
let personaAuthorizationReceipt: unknown = null
try {
personaAuthorizationReceipt = await loadPersonaControlAuthorization(authorizationExpectation)
} catch {
// Authorization discovery is fail-closed but does not prevent safe,
// system-direct partner planning.
let nativeAuthorizationVerification: unknown = null
if (personaControlAuthorizationReceipt !== undefined) {
try {
nativeAuthorizationVerification = await verifyNativePersonaControlAuthorization({
receipt: personaControlAuthorizationReceipt,
expectation: {
...authorizationExpectation,
observedAtMilliseconds: authorizationObservedAt ?? Date.now(),
},
})
} catch {
// Native authorization verification is fail-closed but does not
// prevent safe, system-direct partner planning.
}
}
const personaAuthorized = hasPersonaPrimaryControlAuthorization(
personaAuthorizationReceipt,
{
...authorizationExpectation,
observedAt: authorizationObservedAt,
},
)
const personaAuthorized = hasPersonaPrimaryControlAuthorization(nativeAuthorizationVerification)
const goal = await planGoal({
utterance,
personaAuthorized,