persona: receipt Discord bridge commands
This commit is contained in:
parent
514c2a6cb9
commit
cb3239aaac
2 changed files with 2 additions and 2 deletions
|
|
@ -22,7 +22,7 @@ test("receipt signer and permit requester are distinct domain identities",async(
|
|||
|
||||
test("delivery listener admits absent typed stores when their owner directory is writable",async()=>{const root=await mkdtemp(resolve(tmpdir(),"persona-mouth-readiness-")),runtime=loadRuntime(),requestIdentity=await enrollDomainIdentity(resolve(root,"request.seed"),"request identity",requestIdentityDomain),mouthAnchor=resolve(root,"mouth-anchor.msgpack"),permitAnchor=resolve(root,"permit-anchor.msgpack"),receiptKey=resolve(root,"receipt.seed"),permitKey=resolve(root,"permit.seed");await writeFile(mouthAnchor,runtime.encode(requestAnchor(requestIdentity)));await writeFile(permitAnchor,new Uint8Array([1]));await enrollPersonaDiscordDeliveryIdentity({runtime,privateKeyPath:receiptKey,trustAnchorOut:resolve(root,"receipt-anchor.msgpack")});await exportPersonaDiscordPermitRequestAnchor({runtime,privateKeyPath:permitKey,trustAnchorOut:resolve(root,"permit-request-anchor.msgpack")});const readiness=await inspectPersonaDiscordDeliveryReadiness({runtime,requestStorePath:resolve(root,"requests.cc"),receiptStorePath:resolve(root,"receipts.cc"),executionStorePath:resolve(root,"executions.cc"),epiphanyTrustAnchorPath:mouthAnchor,epiphanyRuntimeId:"epiphany-yggdrasil",epiphanyPermitAnchorPath:permitAnchor,epiphanyPermitEndpoint:"rudp://127.0.0.1:9",privateKeyPath:receiptKey,permitRequesterPrivateKeyPath:permitKey});assert.equal(readiness.ready,true);assert.deepEqual(readiness.reasons,[]);});
|
||||
|
||||
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("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)},i=process.argv.indexOf('--cultmesh-command-id');if(i<0||process.argv[i+1]!=='request-1')process.exit(2);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,"");});
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ export async function processPersonaDiscordDeliveries(options){
|
|||
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 accessStoreOrWritableParent(options.requestStorePath,constants.R_OK);await accessStoreOrWritableParent(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||expires<now)throw new Error("request is expired");if(expires-issued>120_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;}
|
||||
function post(request,options){const args=[options.bridgeCliPath,"discord-post","--cultmesh-command-id",request.requestId,"--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;}
|
||||
async function terminal(status,request,digest,detail,c){const now=new Date().toISOString(),posted=c.posted??{},receipt={schemaName:"bifrost.persona_discord_delivery_receipt",schemaVersion:receiptSchema,receiptId:request.requestId,requestId:request.requestId,requestPayloadSha256:digest,status,channelId:request.channelId,replyToMessageId:request.replyToMessageId,messageId:status==="completed"?posted.messageId:"",transport:status==="completed"?posted.transport:"",crossingReceiptId:status==="completed"?posted.crossingReceiptId:"",receiptUrl:status==="completed"?posted.url:"",completedAt:now,providerIdentityId:c.identity.identityId,privateStateExposed:false,providerSignature:new Uint8Array()};receipt.providerSignature=new Uint8Array(signDomainPurpose(c.identity,receiptSignatureDomain,receiptSigningPurpose,c.runtime.encode(receiptSigningTuple(receipt))));await c.receipts.cache.put(c.defs.receipt,receipt.receiptId,receipt);await c.executions.cache.put(c.defs.execution,request.requestId,{schemaName:"bifrost.persona_discord_delivery_execution",schemaVersion:executionSchema,requestId:request.requestId,requestPayloadSha256:digest,status,recordedAt:now,detail});return receipt;}
|
||||
async function open(runtime,definition,path){await mkdir(dirname(path),{recursive:true});const cache=runtime.CultCache.builder().withDocumentType(definition).withGenericStore(new runtime.SingleFileMessagePackBackingStore(path)).build();await cache.pullAllBackingStores();return {cache};}
|
||||
async function accessStoreOrWritableParent(path,mode){try{await access(path,mode);}catch(error){if(!error||typeof error!=="object"||error.code!=="ENOENT")throw error;await access(dirname(path),constants.W_OK);}}
|
||||
|
|
|
|||
Loading…
Reference in a new issue