diff --git a/tests/persona-discord-delivery.test.mjs b/tests/persona-discord-delivery.test.mjs index 44461f3..7ffe531 100644 --- a/tests/persona-discord-delivery.test.mjs +++ b/tests/persona-discord-delivery.test.mjs @@ -6,7 +6,7 @@ import {mkdtemp,writeFile} from "node:fs/promises"; import {tmpdir} from "node:os"; import {resolve} from "node:path"; import {buildPersonaDiscordDeliveryDefinitions,executionSchema,parseReceipt,parseRequest,receiptDocumentTuple,receiptIdentityDomain,receiptSignatureDomain,receiptSigningPurpose,receiptSigningTuple,requestIdentityDomain,requestSignatureDomain,requestSigningPurpose,requestSigningTuple} from "../tools/persona-discord-delivery-documents.mjs"; -import {processPersonaDiscordDeliveries} from "../tools/persona-discord-delivery.mjs"; +import {enrollPersonaDiscordDeliveryIdentity,exportPersonaDiscordPermitRequestAnchor,processPersonaDiscordDeliveries} from "../tools/persona-discord-delivery.mjs"; import {enrollDomainIdentity,signDomainPurpose,verifyDomainPurpose} from "../tools/bifrost-signed-identity.mjs"; const signature=()=>new Uint8Array(64); @@ -18,6 +18,8 @@ test("Persona delivery receipt terminal states cannot counterfeit delivery",()=> test("Persona delivery documents refuse privacy and identity substitutions",()=>{assert.throws(()=>parseRequest({...request(),privateStateExposed:true}),/must be false/);const receipt={schemaVersion:"bifrost.persona_discord_delivery_receipt.v0",receiptId:"receipt-2",requestId:"request-1",requestPayloadSha256:`sha256-${"b".repeat(64)}`,status:"failed",channelId:"123",replyToMessageId:"",messageId:"",transport:"",crossingReceiptId:"",receiptUrl:"",completedAt:"2026-07-21T12:00:02Z",providerIdentityId:"bifrost",privateStateExposed:false,providerSignature:signature()};assert.throws(()=>parseReceipt(receipt),/must equal requestId/);}); +test("receipt signer and permit requester are distinct domain identities",async()=>{const root=await mkdtemp(resolve(tmpdir(),"persona-mouth-identities-")),runtime=loadRuntime(),receiptKey=resolve(root,"receipt.seed"),permitKey=resolve(root,"permit.seed"),receiptAnchor=resolve(root,"receipt-anchor.msgpack"),permitAnchor=resolve(root,"permit-anchor.msgpack"),receipt=await enrollPersonaDiscordDeliveryIdentity({runtime,privateKeyPath:receiptKey,trustAnchorOut:receiptAnchor}),permit=await exportPersonaDiscordPermitRequestAnchor({runtime,privateKeyPath:permitKey,trustAnchorOut:permitAnchor});assert.notEqual(receiptKey,permitKey);assert.notEqual(receipt.identityId,permit.identityId);}); + test("Bifrost authenticates one request, publishes one signed receipt, and replays without reposting",async()=>{const root=await mkdtemp(resolve(tmpdir(),"persona-mouth-")),runtime=loadRuntime(),defs=buildPersonaDiscordDeliveryDefinitions(runtime.defineDocumentType),requestStore=resolve(root,"requests.cc"),receiptStore=resolve(root,"receipts.cc"),executionStore=resolve(root,"private","executions.cc"),requestKey=resolve(root,"request.seed"),deliveryKey=resolve(root,"delivery.seed"),anchorPath=resolve(root,"request-anchor.msgpack"),counterPath=resolve(root,"post-count"),bridgePath=resolve(root,"fake-bridge.mjs"),requestIdentity=await enrollDomainIdentity(requestKey,"request identity",requestIdentityDomain),deliveryIdentity=await enrollDomainIdentity(deliveryKey,"delivery identity",receiptIdentityDomain);await writeFile(anchorPath,runtime.encode(requestAnchor(requestIdentity)));await writeFile(bridgePath,`import{readFileSync,writeFileSync}from'node:fs';const p=${JSON.stringify(counterPath)};let n=0;try{n=Number(readFileSync(p,'utf8'))}catch{}writeFileSync(p,String(n+1));console.log(JSON.stringify({action:'discord-post',ok:true,channelId:'123',messageId:'789',transport:'bifrost.discord-post',crossingReceiptId:'crossing-1',url:'https://discord.com/channels/g/123/789'}));`);const now=Date.now(),value={...request(),issuedAt:new Date(now-1000).toISOString(),expiresAt:new Date(now+60000).toISOString(),signerIdentityId:requestIdentity.identityId};value.contentSha256=`sha256-${createHash("sha256").update(value.content).digest("hex")}`;value.signerSignature=new Uint8Array(signDomainPurpose(requestIdentity,requestSignatureDomain,requestSigningPurpose,runtime.encode(requestSigningTuple(value))));const requests=await open(runtime,defs.request,requestStore);await requests.put(defs.request,value.requestId,value);const options={runtime,requestStorePath:requestStore,receiptStorePath:receiptStore,executionStorePath:executionStore,epiphanyTrustAnchorPath:anchorPath,epiphanyRuntimeId:"epiphany-yggdrasil",privateKeyPath:deliveryKey,targetAdmitted:()=>true,bridgeCliPath:bridgePath,bifrostRoot:root,requestPermit:async()=>({permitId:"permit-1"})};let results=await processPersonaDiscordDeliveries(options);assert.equal(results[0].status,"completed");results=await processPersonaDiscordDeliveries(options);assert.equal(results.length,0);const receipts=await open(runtime,defs.receipt,receiptStore),receipt=receipts.get(defs.receipt,value.requestId);assert.equal(receipt.messageId,"789");assert.equal(verifyDomainPurpose(deliveryIdentity.publicKey,receiptSignatureDomain,receiptSigningPurpose,runtime.encode(receiptSigningTuple(receipt)),receipt.providerSignature),true);assert.equal(await import("node:fs/promises").then(fs=>fs.readFile(counterPath,"utf8")),"1");}); test("running journal recovers as terminal unknown without a second Discord post",async()=>{const root=await mkdtemp(resolve(tmpdir(),"persona-mouth-crash-")),runtime=loadRuntime(),defs=buildPersonaDiscordDeliveryDefinitions(runtime.defineDocumentType),requestStore=resolve(root,"requests.cc"),receiptStore=resolve(root,"receipts.cc"),executionStore=resolve(root,"private","executions.cc"),requestIdentity=await enrollDomainIdentity(resolve(root,"request.seed"),"request identity",requestIdentityDomain),deliveryKey=resolve(root,"delivery.seed"),anchorPath=resolve(root,"anchor.msgpack");await enrollDomainIdentity(deliveryKey,"delivery identity",receiptIdentityDomain);await writeFile(anchorPath,runtime.encode(requestAnchor(requestIdentity)));const now=Date.now(),value={...request(),issuedAt:new Date(now-1000).toISOString(),expiresAt:new Date(now+60000).toISOString(),signerIdentityId:requestIdentity.identityId};value.contentSha256=`sha256-${createHash("sha256").update(value.content).digest("hex")}`;value.signerSignature=new Uint8Array(signDomainPurpose(requestIdentity,requestSignatureDomain,requestSigningPurpose,runtime.encode(requestSigningTuple(value))));const requests=await open(runtime,defs.request,requestStore);await requests.put(defs.request,value.requestId,value);const digest=`sha256-${createHash("sha256").update(runtime.encode(requestSigningTuple(value))).digest("hex")}`,executions=await open(runtime,defs.execution,executionStore);await executions.put(defs.execution,value.requestId,{schemaName:"bifrost.persona_discord_delivery_execution",schemaVersion:executionSchema,requestId:value.requestId,requestPayloadSha256:digest,status:"running",recordedAt:new Date().toISOString(),detail:"crash fixture"});const results=await processPersonaDiscordDeliveries({runtime,requestStorePath:requestStore,receiptStorePath:receiptStore,executionStorePath:executionStore,epiphanyTrustAnchorPath:anchorPath,epiphanyRuntimeId:"epiphany-yggdrasil",privateKeyPath:deliveryKey,targetAdmitted:()=>true,bridgeCliPath:resolve(root,"must-not-run.mjs"),bifrostRoot:root});assert.equal(results[0].status,"unknown");assert.equal(results[0].messageId,"");}); diff --git a/tools/persona-discord-delivery.mjs b/tools/persona-discord-delivery.mjs index daa5bd6..e11b669 100644 --- a/tools/persona-discord-delivery.mjs +++ b/tools/persona-discord-delivery.mjs @@ -9,7 +9,7 @@ import {requestPersonaDiscordPermit} from "./persona-discord-permit.mjs"; import {permitRequestIdentityDomain,permitRequestPurpose,permitRequestSchema} from "./persona-discord-permit.mjs"; export async function enrollPersonaDiscordDeliveryIdentity({runtime,privateKeyPath,trustAnchorOut}){const identity=await enrollDomainIdentity(privateKeyPath,"Bifrost Persona Discord delivery identity",receiptIdentityDomain),anchor=["gamecult.service_trust_anchor.v1","bifrost-persona-discord-delivery:bifrost-discord-yggdrasil:v0","bifrost-persona-discord-delivery","bifrost-discord-yggdrasil",identity.identityId,new Uint8Array(identity.publicKey),"ed25519",receiptSigningPurpose,"bifrost.persona_discord_delivery_receipt.v0","root",Date.now(),null,false];await mkdir(dirname(trustAnchorOut),{recursive:true});await writeFile(trustAnchorOut,runtime.encode(anchor),{flag:"wx",mode:0o640});return {identityId:identity.identityId,privateKeyPath,trustAnchorOut};} -export async function exportPersonaDiscordPermitRequestAnchor({runtime,privateKeyPath,trustAnchorOut}){const identity=await loadDomainIdentity(privateKeyPath,"Bifrost Persona Discord permit requester identity",permitRequestIdentityDomain),anchor=["gamecult.service_trust_anchor.v1","bifrost-persona-discord-delivery-permit-request:bifrost-discord-yggdrasil:v0","bifrost-persona-discord-delivery","bifrost-discord-yggdrasil",identity.identityId,new Uint8Array(identity.publicKey),"ed25519",permitRequestPurpose,permitRequestSchema,"root",Date.now(),null,false];await mkdir(dirname(trustAnchorOut),{recursive:true});await writeFile(trustAnchorOut,runtime.encode(anchor),{flag:"wx",mode:0o640});return {identityId:identity.identityId,privateKeyPath,trustAnchorOut};} +export async function exportPersonaDiscordPermitRequestAnchor({runtime,privateKeyPath,trustAnchorOut}){let identity;try{await access(privateKeyPath,constants.F_OK);identity=await loadDomainIdentity(privateKeyPath,"Bifrost Persona Discord permit requester identity",permitRequestIdentityDomain);}catch(error){if(!error||typeof error!=="object"||error.code!=="ENOENT")throw error;identity=await enrollDomainIdentity(privateKeyPath,"Bifrost Persona Discord permit requester identity",permitRequestIdentityDomain);}const anchor=["gamecult.service_trust_anchor.v1","bifrost-persona-discord-delivery-permit-request:bifrost-discord-yggdrasil:v0","bifrost-persona-discord-delivery","bifrost-discord-yggdrasil",identity.identityId,new Uint8Array(identity.publicKey),"ed25519",permitRequestPurpose,permitRequestSchema,"root",Date.now(),null,false];await mkdir(dirname(trustAnchorOut),{recursive:true});await writeFile(trustAnchorOut,runtime.encode(anchor),{flag:"wx",mode:0o640});return {identityId:identity.identityId,privateKeyPath,trustAnchorOut};} export async function processPersonaDiscordDeliveries(options){ const {runtime,requestStorePath,receiptStorePath,executionStorePath,epiphanyTrustAnchorPath,privateKeyPath,targetAdmitted}=options,defs=buildPersonaDiscordDeliveryDefinitions(runtime.defineDocumentType); @@ -27,7 +27,7 @@ export async function processPersonaDiscordDeliveries(options){ const permitIssuedAt=existingExecution?.permitIssuedAt??new Date().toISOString(),permitExpiresAt=existingExecution?.permitExpiresAt??new Date(Date.parse(permitIssuedAt)+5000).toISOString(),nonce=existingExecution?.permitNonce??randomBytes(16).toString("hex"); if(!existingExecution)await executions.cache.put(defs.execution,request.requestId,{schemaName:"bifrost.persona_discord_delivery_execution",schemaVersion:executionSchema,requestId:request.requestId,requestPayloadSha256:digest,status:"permit_pending",recordedAt:permitIssuedAt,permitNonce:nonce,permitIssuedAt,permitExpiresAt,detail:"permit request intent persisted before network"}); let permit; - try{permit=await (options.requestPermit??requestPersonaDiscordPermit)({endpoint:options.epiphanyPermitEndpoint,requestId:request.requestId,requestPayloadSha256:digest,targetRuntimeId:request.targetRuntimeId,nonce,issuedAt:permitIssuedAt,expiresAt:permitExpiresAt,privateKeyPath,epiphanyPermitAnchorPath:options.epiphanyPermitAnchorPath,runtime:options.permitRuntime,timeoutMs:options.permitTimeoutMs});} + try{permit=await (options.requestPermit??requestPersonaDiscordPermit)({endpoint:options.epiphanyPermitEndpoint,requestId:request.requestId,requestPayloadSha256:digest,targetRuntimeId:request.targetRuntimeId,nonce,issuedAt:permitIssuedAt,expiresAt:permitExpiresAt,privateKeyPath:options.permitRequesterPrivateKeyPath,epiphanyPermitAnchorPath:options.epiphanyPermitAnchorPath,runtime:options.permitRuntime,timeoutMs:options.permitTimeoutMs});} catch(error){results.push({requestId:request.requestId,status:"permit_pending",detail:error instanceof Error?error.message:String(error)});continue;} await executions.cache.put(defs.execution,request.requestId,{schemaName:"bifrost.persona_discord_delivery_execution",schemaVersion:executionSchema,requestId:request.requestId,requestPayloadSha256:digest,status:"running",recordedAt:new Date().toISOString(),detail:`permit ${permit.permitId} durably consumed before external delivery`}); if(Date.parse(permit.expiresAt)<=Date.now()){results.push(await terminal("failed",request,digest,"Epiphany brake permit expired after durable running journal and before Discord actuation",{runtime,defs,receipts,executions,identity}));continue;} @@ -39,7 +39,7 @@ export async function processPersonaDiscordDeliveries(options){ return results; } -export async function inspectPersonaDiscordDeliveryReadiness(options){try{requireSeparate([options.requestStorePath,options.receiptStorePath,options.executionStorePath]);const anchor=await readAnchor(options.runtime,options.epiphanyTrustAnchorPath,options.epiphanyRuntimeId),identity=await loadDomainIdentity(options.privateKeyPath,"Bifrost Persona Discord delivery identity",receiptIdentityDomain);if(!options.epiphanyPermitEndpoint)throw new Error("Epiphany permit endpoint is absent");await access(options.epiphanyPermitAnchorPath,constants.R_OK);await access(options.requestStorePath,constants.R_OK);await access(options.receiptStorePath,constants.R_OK|constants.W_OK);return {ready:true,requestSignerIdentityId:anchor.identityId,receiptSignerIdentityId:identity.identityId,reasons:[]};}catch(error){return {ready:false,requestSignerIdentityId:"",receiptSignerIdentityId:"",reasons:[error instanceof Error?error.message:String(error)]};}} +export async function inspectPersonaDiscordDeliveryReadiness(options){try{requireSeparate([options.requestStorePath,options.receiptStorePath,options.executionStorePath,options.privateKeyPath,options.permitRequesterPrivateKeyPath]);const anchor=await readAnchor(options.runtime,options.epiphanyTrustAnchorPath,options.epiphanyRuntimeId),identity=await loadDomainIdentity(options.privateKeyPath,"Bifrost Persona Discord delivery identity",receiptIdentityDomain),permitRequester=await loadDomainIdentity(options.permitRequesterPrivateKeyPath,"Bifrost Persona Discord permit requester identity",permitRequestIdentityDomain);if(!options.epiphanyPermitEndpoint)throw new Error("Epiphany permit endpoint is absent");await access(options.epiphanyPermitAnchorPath,constants.R_OK);await access(options.requestStorePath,constants.R_OK);await access(options.receiptStorePath,constants.R_OK|constants.W_OK);return {ready:true,requestSignerIdentityId:anchor.identityId,receiptSignerIdentityId:identity.identityId,permitRequesterIdentityId:permitRequester.identityId,reasons:[]};}catch(error){return {ready:false,requestSignerIdentityId:"",receiptSignerIdentityId:"",permitRequesterIdentityId:"",reasons:[error instanceof Error?error.message:String(error)]};}} function validateRequest(request,unsigned,anchor,targetAdmitted){if(request.signerIdentityId!==anchor.identityId)throw new Error("request signer is not the pinned Epiphany Persona mouth identity");if(!verifyDomainPurpose(anchor.publicKey,requestSignatureDomain,requestSigningPurpose,unsigned,request.signerSignature))throw new Error("request signature is invalid");if(`sha256-${sha(Buffer.from(request.content,"utf8"))}`!==request.contentSha256)throw new Error("request content digest is invalid");const now=Date.now(),issued=Date.parse(request.issuedAt),expires=Date.parse(request.expiresAt);if(issued>now+30_000)throw new Error("request issuance is in the future");if(expires<=issued||expires120_000)throw new Error("request lifetime exceeds 120 seconds");if(!targetAdmitted(request))throw new Error("request target is not an exact configured Bifrost Persona binding");} function post(request,options){const args=[options.bridgeCliPath,"discord-post","--channel-id",request.channelId,"--content",request.content,"--identity",request.personaAgentId,"--source-kind","epiphany_persona_speech","--source-id",request.effectDocumentId,"--authority-ref","epiphany.persona_discord_delivery_request.v0","--epiphany-lane-id","Persona","--epiphany-agent-identity",request.personaAgentId,"--receipt-store",options.crossingReceiptStorePath??resolve(dirname(options.executionStorePath),"persona-discord-crossing-receipts.cc")];if(request.replyToMessageId)args.push("--reply-to-message-id",request.replyToMessageId);const capability=process.env.HEIMDALL_CAPABILITY_REF||process.env.BIFROST_HEIMDALL_CAPABILITY_REF;if(capability)args.push("--heimdall-capability-ref",capability);const result=spawnSync(process.execPath,args,{cwd:options.bifrostRoot,encoding:"utf8",windowsHide:true,env:process.env});if(result.status!==0||result.error)throw new Error(`Bifrost Discord transport returned indeterminate outcome: ${result.error?.message||result.stderr||result.stdout}`);const value=JSON.parse(result.stdout);if(value.action!=="discord-post"||value.ok!==true||value.channelId!==request.channelId||!value.messageId||!value.crossingReceiptId||!value.url)throw new Error("Bifrost Discord transport did not return exact delivery evidence");return value;} diff --git a/tools/persona-feedback.mjs b/tools/persona-feedback.mjs index 6187c2e..4bb508c 100644 --- a/tools/persona-feedback.mjs +++ b/tools/persona-feedback.mjs @@ -87,7 +87,7 @@ async function serveDiscord(){ async function servePersonaDelivery(){const readinessState=await readiness();if(!readinessState.personaDelivery.ready)throw new Error(`Bifrost Persona delivery is not ready: ${readinessState.personaDelivery.reasons.join("; ")}`);const server=await startPersonaDiscordDeliveryRudpServer({runtime,endpoint:requireOption("persona-delivery-rudp-listen"),onRequest:acceptPersonaDeliveryRequest});print({schemaVersion:"bifrost.persona_discord_delivery_listener.v0",status:"ready",runtimeId:"bifrost-discord-yggdrasil",host:server.address.address,port:server.address.port,requestSignerIdentityId:readinessState.personaDelivery.requestSignerIdentityId,receiptSignerIdentityId:readinessState.personaDelivery.receiptSignerIdentityId,privateStateExposed:false});await new Promise(()=>{});} -async function drainPersonaDeliveries(){const n=await privateNode();await n.cache?.pullAllBackingStores?.();const bindings=n.cache.getAll(defs.binding),epiphanyRuntimeId=requireOption("epiphany-runtime-id");return processPersonaDiscordDeliveries({runtime,permitRuntime:{cultnet:runtime.cultnet,msgpack:runtime.msgpack},requestStorePath:personaDeliveryRequestStorePath,receiptStorePath:personaDeliveryReceiptStorePath,executionStorePath:personaDeliveryExecutionStorePath,epiphanyTrustAnchorPath:resolve(requireOption("epiphany-persona-mouth-trust-anchor")),epiphanyRuntimeId,epiphanyPermitAnchorPath:resolve(requireOption("epiphany-persona-permit-trust-anchor")),epiphanyPermitEndpoint:requireOption("epiphany-persona-permit-rudp"),privateKeyPath:resolve(requireOption("persona-delivery-private-key")),bridgeCliPath:resolve(root,"tools","bifrost-bridge.mjs"),bifrostRoot:root,targetAdmitted:request=>request.targetRuntimeId===epiphanyRuntimeId&&request.personaAgentId==="epiphany.Persona"&&bindings.some(binding=>binding.targetRuntimeId===request.targetRuntimeId&&binding.targetPersonaId==="epiphany"&&binding.channelId===request.channelId)});} +async function drainPersonaDeliveries(){const n=await privateNode();await n.cache?.pullAllBackingStores?.();const bindings=n.cache.getAll(defs.binding),epiphanyRuntimeId=requireOption("epiphany-runtime-id");return processPersonaDiscordDeliveries({runtime,permitRuntime:{cultnet:runtime.cultnet,msgpack:runtime.msgpack},requestStorePath:personaDeliveryRequestStorePath,receiptStorePath:personaDeliveryReceiptStorePath,executionStorePath:personaDeliveryExecutionStorePath,epiphanyTrustAnchorPath:resolve(requireOption("epiphany-persona-mouth-trust-anchor")),epiphanyRuntimeId,epiphanyPermitAnchorPath:resolve(requireOption("epiphany-persona-permit-trust-anchor")),epiphanyPermitEndpoint:requireOption("epiphany-persona-permit-rudp"),privateKeyPath:resolve(requireOption("persona-delivery-private-key")),permitRequesterPrivateKeyPath:resolve(requireOption("persona-permit-request-private-key")),bridgeCliPath:resolve(root,"tools","bifrost-bridge.mjs"),bifrostRoot:root,targetAdmitted:request=>request.targetRuntimeId===epiphanyRuntimeId&&request.personaAgentId==="epiphany.Persona"&&bindings.some(binding=>binding.targetRuntimeId===request.targetRuntimeId&&binding.targetPersonaId==="epiphany"&&binding.channelId===request.channelId)});} async function acceptPersonaDeliveryRequest(request){const deliveryDefs=buildPersonaDiscordDeliveryDefinitions(runtime.defineDocumentType),requests=runtime.CultCache.builder().withDocumentType(deliveryDefs.request).withGenericStore(new runtime.SingleFileMessagePackBackingStore(personaDeliveryRequestStorePath)).build();await requests.pullAllBackingStores();const parsed=parsePersonaDiscordDeliveryRequest(request),existing=unwrap(requests.get(deliveryDefs.request,parsed.requestId));if(existing&&stable(existing)!==stable(parsed))throw new Error(`Persona delivery request ${parsed.requestId} conflicts with durable endpoint state.`);if(!existing)await requests.put(deliveryDefs.request,parsed.requestId,parsed);await drainPersonaDeliveries();const receipts=runtime.CultCache.builder().withDocumentType(deliveryDefs.receipt).withGenericStore(new runtime.SingleFileMessagePackBackingStore(personaDeliveryReceiptStorePath)).build();await receipts.pullAllBackingStores();const receipt=unwrap(receipts.get(deliveryDefs.receipt,parsed.requestId));if(!receipt)throw new Error(`Persona delivery request ${parsed.requestId} has no terminal Bifrost receipt yet.`);return receipt;} @@ -105,10 +105,10 @@ async function admitDiscordConversation(conversation,producerRuntimeId){ async function enrollIdentity(){const keyPath=resolve(options["private-key"]||defaultFeedbackIdentityPath()),identity=await enrollBifrostFeedbackIdentity(keyPath),createdAt=new Date().toISOString(),anchor=trustAnchor(identity,createdAt),out=resolve(requireOption("trust-anchor-out")),providerOut=options["provider-health-trust-anchor-out"]?resolve(options["provider-health-trust-anchor-out"]):null;await mkdir(dirname(out),{recursive:true});await writeFile(out,runtime.encode(anchorTuple(anchor)));if(providerOut){if(providerOut===out)throw new Error("Feedback and provider-health trust anchors must use distinct paths.");const providerAnchor=providerHealthTrustAnchor(identity,createdAt);await mkdir(dirname(providerOut),{recursive:true});await writeFile(providerOut,runtime.encode(anchorTuple(providerAnchor)));}print({privateKey:keyPath,trustAnchor:out,identityId:identity.identityId,providerHealthTrustAnchor:providerOut??""});} async function enrollPersonaDeliveryIdentity(){print(await enrollPersonaDiscordDeliveryIdentity({runtime,privateKeyPath:resolve(requireOption("private-key")),trustAnchorOut:resolve(requireOption("trust-anchor-out"))}));} -async function exportPersonaPermitRequestAnchor(){print(await exportPersonaDiscordPermitRequestAnchor({runtime,privateKeyPath:resolve(requireOption("private-key")),trustAnchorOut:resolve(requireOption("trust-anchor-out"))}));} +async function exportPersonaPermitRequestAnchor(){print(await exportPersonaDiscordPermitRequestAnchor({runtime,privateKeyPath:resolve(requireOption("permit-request-private-key")),trustAnchorOut:resolve(requireOption("trust-anchor-out"))}));} async function exportProviderHealthAnchor(){const keyPath=resolve(options["private-key"]||defaultFeedbackIdentityPath()),identity=await loadBifrostFeedbackIdentity(keyPath),out=resolve(requireOption("trust-anchor-out")),anchor=providerHealthTrustAnchor(identity,new Date().toISOString());await mkdir(dirname(out),{recursive:true});await writeFile(out,runtime.encode(anchorTuple(anchor)),{flag:"wx",mode:0o640});print({privateKey:keyPath,trustAnchor:out,identityId:anchor.identityId});} async function exportSnapshot(){const n=await privateNode();await n.cache?.pullAllBackingStores?.();const out=resolve(requireOption("out")),scratch=`${out}.scratch.cc`,cache=runtime.CultCache.builder().withDocumentType(defs.delivery).withDocumentType(defs.receipt).withGenericStore(new runtime.SingleFileMessagePackBackingStore(scratch)).build();for(const value of n.cache.getAll(defs.delivery))await cache.put(defs.delivery,value.admissionId,value);for(const value of n.cache.getAll(defs.receipt))await cache.put(defs.receipt,value.receiptId,value);const registry=new runtime.CultNetDocumentRegistry([runtime.defineCultNetDocumentBinding({definition:defs.delivery,payloadSchemaVersion:feedbackDeliverySchema}),runtime.defineCultNetDocumentBinding({definition:defs.receipt,payloadSchemaVersion:feedbackReceiptSchema})]),message=registry.createRawSnapshotResponse(cache,options["message-id"]||`persona-feedback-${randomUUID()}`);await mkdir(dirname(out),{recursive:true});await writeFile(out,runtime.encode(message));await rm(scratch,{force:true});print({schemaVersion:message.schemaVersion,messageId:message.messageId,documentCount:message.documents.length,out});} -async function readiness(){const ingressReasons=[];let identity,bindingCount=0,pendingFailedCount=0;try{identity=await loadBifrostFeedbackIdentity(resolve(options["private-key"]||defaultFeedbackIdentityPath()));}catch(error){ingressReasons.push(`identity:${error instanceof Error?error.message:String(error)}`);}let bindings=[];try{const info=await stat(privateStorePath);if(!info.isFile())throw new Error("private provider store is not a regular file");const cache=runtime.CultCache.builder().withDocumentType(defs.binding).withDocumentType(defs.actorLink).withDocumentType(defs.receipt).withDocumentType(defs.delivery).withDocumentType(defs.pendingFailure).withGenericStore(new runtime.SingleFileMessagePackBackingStore(privateStorePath)).build();await cache.pullAllBackingStores();bindings=cache.getAll(defs.binding);bindingCount=bindings.length;const admitted=new Set(cache.getAll(defs.receipt).map(value=>value.eventId));pendingFailedCount=cache.getAll(defs.pendingFailure).filter(value=>!admitted.has(value.eventId)).length;if(pendingFailedCount>0)ingressReasons.push(`pendingFailures:${pendingFailedCount}`);if(bindingCount===0)ingressReasons.push("bindings:none");}catch(error){ingressReasons.push(`privateStore:${error instanceof Error?error.message:String(error)}`);}for(const binding of bindings){try{await assertStoreSeparation(binding.deliveryStorePath);try{const info=await stat(binding.deliveryStorePath);if(!info.isFile())throw new Error("delivery path is not a regular file");await access(binding.deliveryStorePath,constants.W_OK);}catch(error){if(!error||typeof error!=="object"||error.code!=="ENOENT")throw error;await access(dirname(binding.deliveryStorePath),constants.W_OK);}}catch(error){ingressReasons.push(`binding:${binding.bindingId}:${error instanceof Error?error.message:String(error)}`);}}const personaConfigured=command==="serve-discord"||command==="serve-persona-delivery"||Boolean(options["epiphany-persona-mouth-trust-anchor"]);let personaDelivery={ready:false,requestSignerIdentityId:"",receiptSignerIdentityId:"",reasons:["not configured"]};if(personaConfigured){personaDelivery=await inspectPersonaDiscordDeliveryReadiness({runtime,requestStorePath:personaDeliveryRequestStorePath,receiptStorePath:personaDeliveryReceiptStorePath,executionStorePath:personaDeliveryExecutionStorePath,epiphanyTrustAnchorPath:resolve(requireOption("epiphany-persona-mouth-trust-anchor")),epiphanyRuntimeId:requireOption("epiphany-runtime-id"),epiphanyPermitAnchorPath:resolve(requireOption("epiphany-persona-permit-trust-anchor")),epiphanyPermitEndpoint:requireOption("epiphany-persona-permit-rudp"),privateKeyPath:resolve(requireOption("persona-delivery-private-key"))});}const reasons=[...ingressReasons,...(personaConfigured?personaDelivery.reasons.map(reason=>`personaDelivery:${reason}`):[])],ingressReady=ingressReasons.length===0;return {schemaVersion:"bifrost.persona_feedback.readiness.v0",providerIdentityId:identity?.identityId??"",bindingCount,pendingFailedCount,ingressReady,ingressReasons,personaDelivery,status:ingressReady&&(!personaConfigured||personaDelivery.ready)?"ready":"degraded",reasons,privateStateExposed:false};} +async function readiness(){const ingressReasons=[];let identity,bindingCount=0,pendingFailedCount=0;try{identity=await loadBifrostFeedbackIdentity(resolve(options["private-key"]||defaultFeedbackIdentityPath()));}catch(error){ingressReasons.push(`identity:${error instanceof Error?error.message:String(error)}`);}let bindings=[];try{const info=await stat(privateStorePath);if(!info.isFile())throw new Error("private provider store is not a regular file");const cache=runtime.CultCache.builder().withDocumentType(defs.binding).withDocumentType(defs.actorLink).withDocumentType(defs.receipt).withDocumentType(defs.delivery).withDocumentType(defs.pendingFailure).withGenericStore(new runtime.SingleFileMessagePackBackingStore(privateStorePath)).build();await cache.pullAllBackingStores();bindings=cache.getAll(defs.binding);bindingCount=bindings.length;const admitted=new Set(cache.getAll(defs.receipt).map(value=>value.eventId));pendingFailedCount=cache.getAll(defs.pendingFailure).filter(value=>!admitted.has(value.eventId)).length;if(pendingFailedCount>0)ingressReasons.push(`pendingFailures:${pendingFailedCount}`);if(bindingCount===0)ingressReasons.push("bindings:none");}catch(error){ingressReasons.push(`privateStore:${error instanceof Error?error.message:String(error)}`);}for(const binding of bindings){try{await assertStoreSeparation(binding.deliveryStorePath);try{const info=await stat(binding.deliveryStorePath);if(!info.isFile())throw new Error("delivery path is not a regular file");await access(binding.deliveryStorePath,constants.W_OK);}catch(error){if(!error||typeof error!=="object"||error.code!=="ENOENT")throw error;await access(dirname(binding.deliveryStorePath),constants.W_OK);}}catch(error){ingressReasons.push(`binding:${binding.bindingId}:${error instanceof Error?error.message:String(error)}`);}}const personaConfigured=command==="serve-discord"||command==="serve-persona-delivery"||Boolean(options["epiphany-persona-mouth-trust-anchor"]);let personaDelivery={ready:false,requestSignerIdentityId:"",receiptSignerIdentityId:"",permitRequesterIdentityId:"",reasons:["not configured"]};if(personaConfigured){personaDelivery=await inspectPersonaDiscordDeliveryReadiness({runtime,requestStorePath:personaDeliveryRequestStorePath,receiptStorePath:personaDeliveryReceiptStorePath,executionStorePath:personaDeliveryExecutionStorePath,epiphanyTrustAnchorPath:resolve(requireOption("epiphany-persona-mouth-trust-anchor")),epiphanyRuntimeId:requireOption("epiphany-runtime-id"),epiphanyPermitAnchorPath:resolve(requireOption("epiphany-persona-permit-trust-anchor")),epiphanyPermitEndpoint:requireOption("epiphany-persona-permit-rudp"),privateKeyPath:resolve(requireOption("persona-delivery-private-key")),permitRequesterPrivateKeyPath:resolve(requireOption("persona-permit-request-private-key"))});}const reasons=[...ingressReasons,...(personaConfigured?personaDelivery.reasons.map(reason=>`personaDelivery:${reason}`):[])],ingressReady=ingressReasons.length===0;return {schemaVersion:"bifrost.persona_feedback.readiness.v0",providerIdentityId:identity?.identityId??"",bindingCount,pendingFailedCount,ingressReady,ingressReasons,personaDelivery,status:ingressReady&&(!personaConfigured||personaDelivery.ready)?"ready":"degraded",reasons,privateStateExposed:false};} async function status(){print(await readiness());} function loadRuntime(){const cr=createRequire(resolve(cult,"packages","cultcache-ts","package.json")),mr=createRequire(resolve(cult,"packages","cultmesh-ts","package.json")),nr=createRequire(resolve(cult,"packages","cultnet-ts","package.json")),cc=cr("cultcache-ts"),cn=nr("cultnet-ts"),mp=nr("@msgpack/msgpack");return {CultMesh:mr("cultmesh-ts").CultMesh,CultCache:cc.CultCache,SingleFileMessagePackBackingStore:cc.SingleFileMessagePackBackingStore,defineDocumentType:cc.defineDocumentType,CultNetDocumentRegistry:cn.CultNetDocumentRegistry,defineCultNetDocumentBinding:cn.defineCultNetDocumentBinding,cultnet:cn,msgpack:mp,encode:mp.encode,decode:mp.decode};}