feat: bind persona control to authorization receipts
This commit is contained in:
parent
22095d6848
commit
099ed036b2
12 changed files with 248 additions and 15 deletions
|
|
@ -55,9 +55,8 @@ describe('PersonaLanguageShellPanel', () => {
|
|||
personaId: 'ICE-P-ZY001',
|
||||
repositoryPaths: ['/persona'],
|
||||
providers: [provider],
|
||||
personaAuthorized: false,
|
||||
developmentId: 'DEV-20260811-010',
|
||||
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.9',
|
||||
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.10',
|
||||
}))
|
||||
expect(await screen.findByText('我还不能执行:需要当前证据。')).toBeInTheDocument()
|
||||
expect(screen.getByText('核验当前事实后规划下一步')).toBeInTheDocument()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
type LanguageShellViewState,
|
||||
} from './HotPluggableLanguageShell'
|
||||
|
||||
const CURRENT_ARCHITECTURE_ANCHOR = 'HLP-CURRENT-ARCH-001@2026-08-12.9'
|
||||
const CURRENT_ARCHITECTURE_ANCHOR = 'HLP-CURRENT-ARCH-001@2026-08-12.10'
|
||||
const DEVELOPMENT_ID = 'DEV-20260811-010'
|
||||
|
||||
type Planner = typeof planPersonaLanguageShellGoal
|
||||
|
|
@ -103,9 +103,8 @@ export function PersonaLanguageShellPanel({
|
|||
personaId,
|
||||
repositoryPaths,
|
||||
providers,
|
||||
// No desktop authorization receipt is wired yet, so this source slice
|
||||
// must remain system-direct even after partner deliberation succeeds.
|
||||
personaAuthorized: false,
|
||||
// No native authorization receipt loader is wired yet. The controller
|
||||
// therefore remains system-direct even after deliberation succeeds.
|
||||
requestId: requestId(),
|
||||
modelInstanceId: `hololake-desktop:${personaId}`,
|
||||
developmentId: DEVELOPMENT_ID,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
PERSONA_CONTROL_AUTHORIZATION_SCHEMA,
|
||||
PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE,
|
||||
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.10',
|
||||
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),
|
||||
}
|
||||
|
||||
describe('hasPersonaPrimaryControlAuthorization', () => {
|
||||
it('accepts only an exact, current, Guanghu OS verified planning receipt', () => {
|
||||
expect(hasPersonaPrimaryControlAuthorization(receipt, expected)).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' }],
|
||||
])('fails closed for %s evidence', (_label, candidate) => {
|
||||
expect(hasPersonaPrimaryControlAuthorization(candidate, expected)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
export const PERSONA_CONTROL_AUTHORIZATION_SCHEMA = 'hololake.persona-control-authorization/v1' as const
|
||||
export const PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE = 'PERSONA_PRIMARY_LANGUAGE_PLANNING' as const
|
||||
|
||||
export type PersonaControlAuthorizationReceipt = {
|
||||
schema: typeof PERSONA_CONTROL_AUTHORIZATION_SCHEMA
|
||||
outcome: 'VERIFIED'
|
||||
authorizationId: string
|
||||
verifier: 'GUANGHU_OS'
|
||||
scope: typeof PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE
|
||||
personaId: string
|
||||
humanResponsibilitySubject: string
|
||||
repositoryHead: string
|
||||
modelInstanceId: string
|
||||
requestId: string
|
||||
sourceLanguageAnchor: string
|
||||
issuedAt: string
|
||||
validUntil: string
|
||||
evidenceDigest: string
|
||||
}
|
||||
|
||||
export type PersonaControlAuthorizationExpectation = {
|
||||
personaId: string
|
||||
humanResponsibilitySubject: string
|
||||
repositoryHead: string
|
||||
modelInstanceId: string
|
||||
requestId: string
|
||||
sourceLanguageAnchor: string
|
||||
observedAt?: number
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function exactString(record: Record<string, unknown>, key: string, expected: string): boolean {
|
||||
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.
|
||||
*/
|
||||
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, '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 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 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
|
||||
}
|
||||
|
|
@ -96,7 +96,6 @@ function input() {
|
|||
personaId: 'ICE-P-ZY001',
|
||||
repositoryPaths: ['/persona/zhuyuan'],
|
||||
providers: [provider],
|
||||
personaAuthorized: true,
|
||||
requestId: 'REQ-001',
|
||||
modelInstanceId: 'MODEL-INSTANCE-001',
|
||||
developmentId: 'DEV-20260811-010',
|
||||
|
|
@ -173,6 +172,41 @@ describe('planPersonaLanguageShellGoal', () => {
|
|||
})
|
||||
expect(loadDeviceIdentity).toHaveBeenCalledTimes(1)
|
||||
expect(planGoal).toHaveBeenCalledTimes(1)
|
||||
expect(planGoal).toHaveBeenCalledWith(expect.objectContaining({ personaAuthorized: false }))
|
||||
})
|
||||
|
||||
it('enables persona-primary planning only for an exact request-bound authorization receipt', async () => {
|
||||
const planGoal = vi.fn().mockResolvedValue(plannedGoal)
|
||||
|
||||
await planPersonaLanguageShellGoal({
|
||||
...input(),
|
||||
loadPersonaControlAuthorization: vi.fn().mockResolvedValue({
|
||||
schema: 'hololake.persona-control-authorization/v1',
|
||||
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),
|
||||
}),
|
||||
authorizationObservedAt: Date.parse('2026-08-12T04:35:00+08:00'),
|
||||
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: true }))
|
||||
})
|
||||
|
||||
it('returns a stable binding error and never invokes cognition for invalid device evidence', async () => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { invoke } from '@tauri-apps/api/core'
|
|||
import { isTauri, mockInvoke } from '../mock-tauri'
|
||||
import type { AiModelProvider } from './aiTargets'
|
||||
import type { LanguageGoalProjection, RealityBoundary } from './languageOperatingModel'
|
||||
import { hasPersonaPrimaryControlAuthorization } from './personaControlAuthorization'
|
||||
import {
|
||||
planPersonaLanguageGoal,
|
||||
type PersonaLanguageGoalBinding,
|
||||
|
|
@ -34,6 +35,14 @@ 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>
|
||||
|
||||
async function loadNativeDeviceIdentity(): Promise<unknown> {
|
||||
return isTauri()
|
||||
|
|
@ -41,6 +50,10 @@ async function loadNativeDeviceIdentity(): Promise<unknown> {
|
|||
: mockInvoke<unknown>('guanghu_router_device_identity')
|
||||
}
|
||||
|
||||
async function loadNoPersonaControlAuthorization(): Promise<null> {
|
||||
return null
|
||||
}
|
||||
|
||||
function errorCode(error: unknown, fallback: string): string {
|
||||
return error instanceof Error && error.message.trim() ? error.message : fallback
|
||||
}
|
||||
|
|
@ -66,7 +79,7 @@ export async function planPersonaLanguageShellGoal({
|
|||
personaId,
|
||||
repositoryPaths,
|
||||
providers,
|
||||
personaAuthorized,
|
||||
authorizationObservedAt,
|
||||
detectedBoundaries,
|
||||
requestId,
|
||||
modelInstanceId,
|
||||
|
|
@ -74,13 +87,14 @@ export async function planPersonaLanguageShellGoal({
|
|||
sourceLanguageAnchor,
|
||||
resolveRepository = resolvePersonaRepositoryBinding,
|
||||
loadDeviceIdentity = loadNativeDeviceIdentity,
|
||||
loadPersonaControlAuthorization = loadNoPersonaControlAuthorization,
|
||||
planGoal = planPersonaLanguageGoal,
|
||||
}: {
|
||||
utterance: string
|
||||
personaId: string
|
||||
repositoryPaths: readonly string[]
|
||||
providers: readonly AiModelProvider[]
|
||||
personaAuthorized: boolean
|
||||
authorizationObservedAt?: number
|
||||
detectedBoundaries?: RealityBoundary[]
|
||||
requestId: string
|
||||
modelInstanceId: string
|
||||
|
|
@ -88,6 +102,7 @@ export async function planPersonaLanguageShellGoal({
|
|||
sourceLanguageAnchor: string
|
||||
resolveRepository?: RepositoryResolver
|
||||
loadDeviceIdentity?: DeviceIdentityLoader
|
||||
loadPersonaControlAuthorization?: PersonaControlAuthorizationLoader
|
||||
planGoal?: GoalPlanner
|
||||
}): Promise<PersonaLanguageShellPlan> {
|
||||
const repository = await resolveRepository({ personaId, repositoryPaths })
|
||||
|
|
@ -121,6 +136,28 @@ export async function planPersonaLanguageShellGoal({
|
|||
}
|
||||
|
||||
try {
|
||||
const authorizationExpectation = {
|
||||
personaId: repository.binding.personaId,
|
||||
humanResponsibilitySubject: repository.binding.humanResponsibilitySubject,
|
||||
repositoryHead: repository.binding.gitHead,
|
||||
modelInstanceId,
|
||||
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.
|
||||
}
|
||||
const personaAuthorized = hasPersonaPrimaryControlAuthorization(
|
||||
personaAuthorizationReceipt,
|
||||
{
|
||||
...authorizationExpectation,
|
||||
observedAt: authorizationObservedAt,
|
||||
},
|
||||
)
|
||||
const goal = await planGoal({
|
||||
utterance,
|
||||
personaAuthorized,
|
||||
|
|
|
|||
Loading…
Reference in a new issue