feat: discover verified persona repository bindings
This commit is contained in:
parent
d0201ba09f
commit
b0970ed0d2
18 changed files with 417 additions and 15 deletions
|
|
@ -16,7 +16,7 @@ const wake = {
|
|||
executionRuntime: 'HoloLake-PNCC',
|
||||
developmentId: 'DEV-20260811-010',
|
||||
authorizationScope: 'LOCAL_READ_ONLY',
|
||||
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.2',
|
||||
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.3',
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { resolvePersonaRepositoryBinding } from './personaRepositoryBinding'
|
||||
|
||||
const binding = {
|
||||
personaId: 'ICE-P-ZY001',
|
||||
repositoryPath: '/persona',
|
||||
gitHead: 'a'.repeat(40),
|
||||
repositoryClean: true,
|
||||
brainEntry: 'brain/CORE.hdlp',
|
||||
currentCheckpoint: '.hololake/persona/CURRENT.hdlp',
|
||||
humanResponsibilitySubject: 'ICE-GL∞',
|
||||
modelProviderId: 'provider-1',
|
||||
modelId: 'model-1',
|
||||
modelBaseUrl: 'https://model.invalid/v1',
|
||||
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',
|
||||
},
|
||||
}
|
||||
|
||||
function receipt(bindings: unknown[], failures: unknown[] = []) {
|
||||
return {
|
||||
schema: 'hololake.pncc-repository-discovery/v1',
|
||||
expectedPersonaId: 'ICE-P-ZY001',
|
||||
inspectedRepositoryCount: 2,
|
||||
bindingCount: bindings.length,
|
||||
bindings,
|
||||
failures,
|
||||
}
|
||||
}
|
||||
|
||||
describe('resolvePersonaRepositoryBinding', () => {
|
||||
it('does not call native discovery without mounted repositories', async () => {
|
||||
const discover = vi.fn()
|
||||
await expect(resolvePersonaRepositoryBinding({
|
||||
personaId: 'ICE-P-ZY001', repositoryPaths: [], discover,
|
||||
})).resolves.toEqual({ phase: 'unavailable', inspectedRepositoryCount: 0, failures: [] })
|
||||
expect(discover).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns the one evidence-verified persona repository', async () => {
|
||||
const discover = vi.fn().mockResolvedValue(receipt([binding], [{ repositoryPath: '/ordinary', code: 'PERSONA_MANIFEST_READ_FAILED' }]))
|
||||
const result = await resolvePersonaRepositoryBinding({
|
||||
personaId: 'ICE-P-ZY001', repositoryPaths: ['/ordinary', '/persona', '/persona'], discover,
|
||||
})
|
||||
expect(result.phase).toBe('bound')
|
||||
if (result.phase === 'bound') expect(result.binding.repositoryPath).toBe('/persona')
|
||||
expect(discover).toHaveBeenCalledWith('discover_persona_code_channel_repositories', {
|
||||
input: { expectedPersonaId: 'ICE-P-ZY001', repositoryPaths: ['/ordinary', '/persona'] },
|
||||
})
|
||||
})
|
||||
|
||||
it('keeps an ordinary mounted repository unbound', async () => {
|
||||
const result = await resolvePersonaRepositoryBinding({
|
||||
personaId: 'ICE-P-ZY001',
|
||||
repositoryPaths: ['/ordinary'],
|
||||
discover: vi.fn().mockResolvedValue(receipt([], [{ repositoryPath: '/ordinary', code: 'PERSONA_MANIFEST_READ_FAILED' }])),
|
||||
})
|
||||
expect(result.phase).toBe('unbound')
|
||||
})
|
||||
|
||||
it('refuses to choose when more than one persona root validates', async () => {
|
||||
const result = await resolvePersonaRepositoryBinding({
|
||||
personaId: 'ICE-P-ZY001',
|
||||
repositoryPaths: ['/persona-a', '/persona-b'],
|
||||
discover: vi.fn().mockResolvedValue(receipt([
|
||||
{ ...binding, repositoryPath: '/persona-a' },
|
||||
{ ...binding, repositoryPath: '/persona-b' },
|
||||
])),
|
||||
})
|
||||
expect(result.phase).toBe('ambiguous')
|
||||
})
|
||||
|
||||
it('fails closed on a mismatched or malformed receipt', async () => {
|
||||
const result = await resolvePersonaRepositoryBinding({
|
||||
personaId: 'ICE-P-ZY001',
|
||||
repositoryPaths: ['/persona'],
|
||||
discover: vi.fn().mockResolvedValue(receipt([{ ...binding, personaId: 'OTHER' }])),
|
||||
})
|
||||
expect(result).toMatchObject({ phase: 'error', code: 'PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID' })
|
||||
})
|
||||
})
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { isTauri, mockInvoke } from '../mock-tauri'
|
||||
|
||||
export interface PersonaRepositoryBinding {
|
||||
personaId: string
|
||||
repositoryPath: string
|
||||
gitHead: string
|
||||
repositoryClean: boolean
|
||||
brainEntry: string
|
||||
currentCheckpoint: string
|
||||
humanResponsibilitySubject: string
|
||||
modelProviderId: string
|
||||
modelId: string
|
||||
modelBaseUrl: string
|
||||
cognitiveGravity: {
|
||||
schema: 'hololake.persona-cognitive-gravity-evidence/v1'
|
||||
subjectPersonaId: string
|
||||
sourcePath: string
|
||||
sourceHash: string
|
||||
frameSchema: 'guanghu.zhuyuan-cognitive-gravity-frame/v1'
|
||||
}
|
||||
}
|
||||
|
||||
export interface PersonaRepositoryBindingFailure {
|
||||
repositoryPath: string
|
||||
code: string
|
||||
}
|
||||
|
||||
export type PersonaRepositoryBindingResolution =
|
||||
| { phase: 'unavailable'; inspectedRepositoryCount: 0; failures: [] }
|
||||
| { phase: 'unbound'; inspectedRepositoryCount: number; failures: PersonaRepositoryBindingFailure[] }
|
||||
| { phase: 'ambiguous'; inspectedRepositoryCount: number; failures: PersonaRepositoryBindingFailure[] }
|
||||
| { phase: 'bound'; inspectedRepositoryCount: number; binding: PersonaRepositoryBinding; failures: PersonaRepositoryBindingFailure[] }
|
||||
| { phase: 'error'; inspectedRepositoryCount: number; failures: PersonaRepositoryBindingFailure[]; code: string }
|
||||
|
||||
type DiscoveryRunner = (
|
||||
command: 'discover_persona_code_channel_repositories',
|
||||
args: { input: { expectedPersonaId: string; repositoryPaths: string[] } },
|
||||
) => Promise<unknown>
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
function requiredString(record: Record<string, unknown>, key: string): string {
|
||||
const value = Reflect.get(record, key)
|
||||
if (typeof value !== 'string' || !value.trim()) throw new Error('PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID')
|
||||
return value
|
||||
}
|
||||
|
||||
function requiredNumber(record: Record<string, unknown>, key: string): number {
|
||||
const value = Reflect.get(record, key)
|
||||
if (!Number.isSafeInteger(value) || (value as number) < 0) {
|
||||
throw new Error('PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID')
|
||||
}
|
||||
return value as number
|
||||
}
|
||||
|
||||
function parseFailure(value: unknown): PersonaRepositoryBindingFailure {
|
||||
if (!isRecord(value)) throw new Error('PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID')
|
||||
return { repositoryPath: requiredString(value, 'repositoryPath'), code: requiredString(value, 'code') }
|
||||
}
|
||||
|
||||
function parseBinding(value: unknown, expectedPersonaId: string): PersonaRepositoryBinding {
|
||||
if (!isRecord(value)) throw new Error('PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID')
|
||||
const personaId = requiredString(value, 'personaId')
|
||||
const gravity = Reflect.get(value, 'cognitiveGravity')
|
||||
if (personaId !== expectedPersonaId || !isRecord(gravity)) {
|
||||
throw new Error('PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID')
|
||||
}
|
||||
const gravitySchema = requiredString(gravity, 'schema')
|
||||
const frameSchema = requiredString(gravity, 'frameSchema')
|
||||
const sourceHash = requiredString(gravity, 'sourceHash')
|
||||
if (
|
||||
gravitySchema !== 'hololake.persona-cognitive-gravity-evidence/v1'
|
||||
|| frameSchema !== 'guanghu.zhuyuan-cognitive-gravity-frame/v1'
|
||||
|| !/^[a-f0-9]{64}$/.test(sourceHash)
|
||||
|| requiredString(gravity, 'subjectPersonaId') !== expectedPersonaId
|
||||
) {
|
||||
throw new Error('PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID')
|
||||
}
|
||||
const repositoryClean = Reflect.get(value, 'repositoryClean')
|
||||
if (typeof repositoryClean !== 'boolean') throw new Error('PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID')
|
||||
return {
|
||||
personaId,
|
||||
repositoryPath: requiredString(value, 'repositoryPath'),
|
||||
gitHead: requiredString(value, 'gitHead'),
|
||||
repositoryClean,
|
||||
brainEntry: requiredString(value, 'brainEntry'),
|
||||
currentCheckpoint: requiredString(value, 'currentCheckpoint'),
|
||||
humanResponsibilitySubject: requiredString(value, 'humanResponsibilitySubject'),
|
||||
modelProviderId: requiredString(value, 'modelProviderId'),
|
||||
modelId: requiredString(value, 'modelId'),
|
||||
modelBaseUrl: requiredString(value, 'modelBaseUrl'),
|
||||
cognitiveGravity: {
|
||||
schema: gravitySchema,
|
||||
subjectPersonaId: expectedPersonaId,
|
||||
sourcePath: requiredString(gravity, 'sourcePath'),
|
||||
sourceHash,
|
||||
frameSchema,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async function runNativeDiscovery(
|
||||
command: 'discover_persona_code_channel_repositories',
|
||||
args: { input: { expectedPersonaId: string; repositoryPaths: string[] } },
|
||||
): Promise<unknown> {
|
||||
return isTauri() ? invoke<unknown>(command, args) : mockInvoke<unknown>(command, args)
|
||||
}
|
||||
|
||||
export async function resolvePersonaRepositoryBinding({
|
||||
personaId,
|
||||
repositoryPaths,
|
||||
discover = runNativeDiscovery,
|
||||
}: {
|
||||
personaId: string
|
||||
repositoryPaths: readonly string[]
|
||||
discover?: DiscoveryRunner
|
||||
}): Promise<PersonaRepositoryBindingResolution> {
|
||||
const candidates = [...new Set(repositoryPaths.map((path) => path.trim()).filter(Boolean))]
|
||||
if (candidates.length === 0) {
|
||||
return { phase: 'unavailable', inspectedRepositoryCount: 0, failures: [] }
|
||||
}
|
||||
try {
|
||||
const value = await discover('discover_persona_code_channel_repositories', {
|
||||
input: { expectedPersonaId: personaId, repositoryPaths: candidates },
|
||||
})
|
||||
if (!isRecord(value)
|
||||
|| requiredString(value, 'schema') !== 'hololake.pncc-repository-discovery/v1'
|
||||
|| requiredString(value, 'expectedPersonaId') !== personaId) {
|
||||
throw new Error('PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID')
|
||||
}
|
||||
const inspectedRepositoryCount = requiredNumber(value, 'inspectedRepositoryCount')
|
||||
const bindingCount = requiredNumber(value, 'bindingCount')
|
||||
const rawBindings = Reflect.get(value, 'bindings')
|
||||
const rawFailures = Reflect.get(value, 'failures')
|
||||
if (!Array.isArray(rawBindings) || !Array.isArray(rawFailures) || bindingCount !== rawBindings.length) {
|
||||
throw new Error('PERSONA_REPOSITORY_DISCOVERY_RECEIPT_INVALID')
|
||||
}
|
||||
const bindings = rawBindings.map((binding) => parseBinding(binding, personaId))
|
||||
const failures = rawFailures.map(parseFailure)
|
||||
if (bindings.length === 0) return { phase: 'unbound', inspectedRepositoryCount, failures }
|
||||
if (bindings.length > 1) return { phase: 'ambiguous', inspectedRepositoryCount, failures }
|
||||
return { phase: 'bound', inspectedRepositoryCount, binding: bindings[0], failures }
|
||||
} catch (error) {
|
||||
return {
|
||||
phase: 'error',
|
||||
inspectedRepositoryCount: candidates.length,
|
||||
failures: [],
|
||||
code: error instanceof Error ? error.message : 'PERSONA_REPOSITORY_DISCOVERY_FAILED',
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue