feat: discover verified persona repository bindings
This commit is contained in:
parent
d0201ba09f
commit
b0970ed0d2
18 changed files with 417 additions and 15 deletions
|
|
@ -103,6 +103,9 @@ REVISE | REFUSE`。
|
|||
`PROCEED | RESEARCH | REVISE | REFUSE`,并把后三者保持为不可执行状态。
|
||||
- 自然语言到 PNCC 生命周期的源码适配器为 `100`:只接收带独立 `real_purpose` 的完整生命周期回执,
|
||||
不在前端用摘要或关键词猜目标;该适配器尚未挂载桌面语言入口。
|
||||
- 人格仓库绑定发现源码为 `100`:桌面候选 Git 仓库必须逐一通过 persona ID、manifest、提交头、脑入口、
|
||||
B0、检查点、模型绑定与器官契约核验;零个候选保持未绑定,多个候选保持歧义,均不得自动选择或唤醒。
|
||||
当前官方工作范围内没有发现 `ICE-P-ZY001` 的真实 manifest,因此真实人格仓库绑定仍为 `0`。
|
||||
- 完整 HoloLake Runtime 与单 AGE 纵向闭环仍为 `0`:桌面语言入口绑定、真实人格仓库 manifest 绑定和
|
||||
桌面运行验收尚未完成,因此不能用本轮源码测试冒充可用产品。
|
||||
- Mirror runner、制品、部署和运行健康:`0`。
|
||||
|
|
|
|||
|
|
@ -526,6 +526,7 @@ macro_rules! app_invoke_handler {
|
|||
guanghu_living_system::guanghu_living_system_plan,
|
||||
persona_code_channel::prepare_persona_code_channel_wake,
|
||||
persona_code_channel::inspect_persona_code_channel_manifest,
|
||||
persona_code_channel::discover_persona_code_channel_repositories,
|
||||
persona_code_channel::query_persona_code_channel_runtime,
|
||||
persona_code_channel::run_persona_code_channel_fact_task,
|
||||
persona_code_channel::run_persona_code_channel_memory_metabolism,
|
||||
|
|
|
|||
|
|
@ -187,11 +187,40 @@ pub struct PersonaManifestInspectionReceipt {
|
|||
pub brain_entry: String,
|
||||
pub current_checkpoint: String,
|
||||
pub human_responsibility_subject: String,
|
||||
pub cognitive_gravity: PersonaCognitiveGravityEvidence,
|
||||
pub model_provider_id: String,
|
||||
pub model_id: String,
|
||||
pub model_base_url: String,
|
||||
pub git_author_name: String,
|
||||
pub git_author_email: String,
|
||||
pub organ_contracts: Vec<PersonaOrganContract>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaRepositoryDiscoveryInput {
|
||||
pub expected_persona_id: String,
|
||||
pub repository_paths: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaRepositoryDiscoveryFailure {
|
||||
pub repository_path: String,
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaRepositoryDiscoveryReceipt {
|
||||
pub schema: &'static str,
|
||||
pub expected_persona_id: String,
|
||||
pub inspected_repository_count: usize,
|
||||
pub binding_count: usize,
|
||||
pub bindings: Vec<PersonaManifestInspectionReceipt>,
|
||||
pub failures: Vec<PersonaRepositoryDiscoveryFailure>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PersonaRuntimeQueryInput {
|
||||
|
|
@ -906,7 +935,7 @@ fn inspect_manifest_at(
|
|||
validate_git_identity(&manifest.git_identity)?;
|
||||
validate_model_binding(&manifest.model_binding)?;
|
||||
repository_file(&repository, &manifest.brain_entry)?;
|
||||
cognitive_gravity_evidence(&repository, &manifest)?;
|
||||
let cognitive_gravity = cognitive_gravity_evidence(&repository, &manifest)?;
|
||||
repository_file(&repository, &manifest.current_checkpoint)?;
|
||||
let mut contracts = Vec::with_capacity(manifest.organs.len());
|
||||
for organ in &manifest.organs {
|
||||
|
|
@ -927,12 +956,68 @@ fn inspect_manifest_at(
|
|||
brain_entry: manifest.brain_entry,
|
||||
current_checkpoint: manifest.current_checkpoint,
|
||||
human_responsibility_subject: manifest.human_responsibility_subject,
|
||||
cognitive_gravity,
|
||||
model_provider_id: manifest.model_binding.provider_id,
|
||||
model_id: manifest.model_binding.model_id,
|
||||
model_base_url: manifest.model_binding.base_url,
|
||||
git_author_name: manifest.git_identity.author_name,
|
||||
git_author_email: manifest.git_identity.author_email,
|
||||
organ_contracts: contracts,
|
||||
})
|
||||
}
|
||||
|
||||
fn discover_persona_repositories_at(
|
||||
input: PersonaRepositoryDiscoveryInput,
|
||||
) -> Result<PersonaRepositoryDiscoveryReceipt, String> {
|
||||
let expected_persona_id = validated_id("EXPECTED_PERSONA_ID", &input.expected_persona_id)?;
|
||||
if input.repository_paths.len() > 64 {
|
||||
return Err("PERSONA_REPOSITORY_DISCOVERY_LIMIT_EXCEEDED".into());
|
||||
}
|
||||
let mut seen = Vec::<PathBuf>::new();
|
||||
let mut bindings = Vec::new();
|
||||
let mut failures = Vec::new();
|
||||
for raw_path in input.repository_paths {
|
||||
let trimmed = raw_path.trim();
|
||||
if trimmed.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let display_path = trimmed.to_string();
|
||||
let (repository, git_head) = match exact_repository(Path::new(trimmed)) {
|
||||
Ok(value) => value,
|
||||
Err(code) => {
|
||||
failures.push(PersonaRepositoryDiscoveryFailure {
|
||||
repository_path: display_path,
|
||||
code,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if seen.iter().any(|candidate| candidate == &repository) {
|
||||
continue;
|
||||
}
|
||||
seen.push(repository.clone());
|
||||
match inspect_manifest_at(PersonaManifestInspectionInput {
|
||||
repository_path: repository.to_string_lossy().into_owned(),
|
||||
expected_persona_id: expected_persona_id.clone(),
|
||||
expected_head: git_head,
|
||||
}) {
|
||||
Ok(receipt) => bindings.push(receipt),
|
||||
Err(code) => failures.push(PersonaRepositoryDiscoveryFailure {
|
||||
repository_path: repository.to_string_lossy().into_owned(),
|
||||
code,
|
||||
}),
|
||||
}
|
||||
}
|
||||
Ok(PersonaRepositoryDiscoveryReceipt {
|
||||
schema: "hololake.pncc-repository-discovery/v1",
|
||||
expected_persona_id,
|
||||
inspected_repository_count: seen.len(),
|
||||
binding_count: bindings.len(),
|
||||
bindings,
|
||||
failures,
|
||||
})
|
||||
}
|
||||
|
||||
fn hex_digest(bytes: &[u8]) -> String {
|
||||
digest(&SHA256, bytes)
|
||||
.as_ref()
|
||||
|
|
@ -3759,6 +3844,13 @@ pub fn inspect_persona_code_channel_manifest(
|
|||
inspect_manifest_at(input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn discover_persona_code_channel_repositories(
|
||||
input: PersonaRepositoryDiscoveryInput,
|
||||
) -> Result<PersonaRepositoryDiscoveryReceipt, String> {
|
||||
discover_persona_repositories_at(input)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn query_persona_code_channel_runtime(
|
||||
input: PersonaRuntimeQueryInput,
|
||||
|
|
@ -6266,6 +6358,9 @@ mod tests {
|
|||
let receipt = inspect_manifest_at(manifest_inspection(repo.path())).unwrap();
|
||||
assert_eq!(receipt.persona_id, "ICE-P-ZY001");
|
||||
assert!(receipt.repository_clean);
|
||||
assert_eq!(receipt.model_provider_id, "fixture-provider");
|
||||
assert_eq!(receipt.model_id, "fixture-model");
|
||||
assert_eq!(receipt.cognitive_gravity.subject_persona_id, "ICE-P-ZY001");
|
||||
assert_eq!(receipt.organ_contracts.len(), 1);
|
||||
let contract = &receipt.organ_contracts[0];
|
||||
assert_eq!(contract.kind, PersonaOrganKind::FactSense);
|
||||
|
|
@ -6276,6 +6371,60 @@ mod tests {
|
|||
assert_eq!(contract.implementation_state, "IMPLEMENTED");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discovers_only_verified_persona_repository_bindings() {
|
||||
let persona = persona_repo();
|
||||
let ordinary = tempfile::TempDir::new().unwrap();
|
||||
run_git(ordinary.path(), &["init", "-b", "main"]);
|
||||
run_git(
|
||||
ordinary.path(),
|
||||
&["config", "user.name", "Ordinary Repository"],
|
||||
);
|
||||
run_git(
|
||||
ordinary.path(),
|
||||
&["config", "user.email", "ordinary@example.invalid"],
|
||||
);
|
||||
fs::write(ordinary.path().join("README.md"), "ordinary repository\n").unwrap();
|
||||
run_git(ordinary.path(), &["add", "."]);
|
||||
run_git(ordinary.path(), &["commit", "-m", "ordinary fixture"]);
|
||||
|
||||
let receipt = discover_persona_repositories_at(PersonaRepositoryDiscoveryInput {
|
||||
expected_persona_id: "ICE-P-ZY001".into(),
|
||||
repository_paths: vec![
|
||||
ordinary.path().to_string_lossy().into_owned(),
|
||||
persona.path().to_string_lossy().into_owned(),
|
||||
persona.path().to_string_lossy().into_owned(),
|
||||
],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(receipt.schema, "hololake.pncc-repository-discovery/v1");
|
||||
assert_eq!(receipt.inspected_repository_count, 2);
|
||||
assert_eq!(receipt.binding_count, 1);
|
||||
assert_eq!(receipt.bindings[0].persona_id, "ICE-P-ZY001");
|
||||
assert_eq!(receipt.failures.len(), 1);
|
||||
assert!(
|
||||
receipt.failures[0]
|
||||
.code
|
||||
.starts_with("PERSONA_FILE_UNAVAILABLE: .hololake/persona/manifest.json:"),
|
||||
"unexpected discovery failure: {}",
|
||||
receipt.failures[0].code,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_persona_repository_discovery_is_a_truthful_non_binding_receipt() {
|
||||
let receipt = discover_persona_repositories_at(PersonaRepositoryDiscoveryInput {
|
||||
expected_persona_id: "ICE-P-ZY001".into(),
|
||||
repository_paths: Vec::new(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(receipt.inspected_repository_count, 0);
|
||||
assert_eq!(receipt.binding_count, 0);
|
||||
assert!(receipt.bindings.is_empty());
|
||||
assert!(receipt.failures.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn declares_but_refuses_an_execution_limb_without_an_executor() {
|
||||
let repo = persona_repo();
|
||||
|
|
|
|||
|
|
@ -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