Add signed Epiphany operator admission boundary

This commit is contained in:
Meta Vens 2026-07-19 12:00:50 +01:00
commit 82dd058d42
9 changed files with 120 additions and 0 deletions

View file

@ -2,3 +2,11 @@
# The default compose.local.yaml is self-contained for local smoke testing.
COMPOSE_PROJECT_NAME=bifrost-local
# Epiphany operator admission. No live transport default is assumed.
BIFROST_EPIPHANY_OPERATOR_REQUEST_STORE=.bifrost/epiphany-operator-requests.cc
BIFROST_EPIPHANY_OPERATOR_PRIVATE_KEY=.bifrost/private/epiphany-operator-ed25519.seed
BIFROST_EPIPHANY_OPERATOR_OWNER_DISCORD_ID=
BIFROST_EPIPHANY_OPERATOR_TARGET_RUNTIME_ID=epiphany-yggdrasil
BIFROST_EPIPHANY_OPERATOR_RESULT_IDENTITY_ID=
BIFROST_EPIPHANY_OPERATOR_CULTMESH_URI=

View file

@ -196,6 +196,10 @@ The command is a dry-run shape probe for public surfaces that are real enough to
VoidBot remains the Discord observer and mention scheduler. A Face registry entry may opt into a remote `remotePersonaFeedbackTarget`; only then does VoidBot insert an immutable typed `voidbot.discord.persona_feedback_event.v0` observation into the dedicated `BIFROST_PERSONA_FEEDBACK_OBSERVATION_STORE_PATH`. This is an observation inbox, never an actuator queue. Insert collisions must contain exactly equal content. Its canonical path and physical file identity must differ from the outbound Discord-command provider store and all Bifrost-private state. Local Persona mention handling is committed first; observation export runs independently with three bounded idempotent attempts and cannot suppress the local response path.
Discord operator control is a separate authority. VoidBot may insert only a strict `voidbot.discord.epiphany_operator_request.v0` document containing one of `status`, `sleep`, `wake`, or `directive`, a fixed target runtime, Discord provenance, and a positive expiry of at most 60 seconds. Bifrost independently requires the exact configured Discord owner and the existing guild/actor link with a Heimdall actor reference. It derives the Rust field-order packet tuple, hashes its canonical MessagePack bytes, and signs the exact `BifrostOperatorCommandAdmission` tuple for purpose `bifrost.operator-command.delivery.v0` with a dedicated operator Ed25519 seed. The feedback seed is not reused.
Bifrost does not mount an Epiphany store and has no argv or shell escape hatch. Operator execution crosses a narrow authenticated transport port. The live CultNet RUDP request/reply adapter is still required; configuration deliberately has no localhost default. Until that adapter exists, admission fails closed. A returned `epiphany.operator_command.result.v0` must arrive under the configured Epiphany transport identity and match command id, packet digest, and target runtime. Bifrost never manufactures success. Ordinary Persona messages remain `feedback_only` and cannot enter this path.
Bifrost admits an observation only when guild, channel, target runtime, canonical repository, Persona, producer, and producer runtime match a preconfigured `bifrost.persona_feedback.target_binding.v0` document in Bifrost's private provider store. That binding also owns source visibility, data classification, and the delivery-store target. Bifrost never infers public visibility from Discord identifiers. It classifies the author using its own `bifrost.discord.actor_link.v0` state: a match yields `linked_governance_feedback`; absence yields `unlinked_social_feedback`. Neither class grants adoption, work, release, or deployment authority.
`tools/persona-feedback.mjs enroll-identity` creates the Bifrost service's Ed25519 seed with exclusive creation and mode `0600`, then exports its public MessagePack trust anchor. The private seed belongs under Bifrost's service-private state (default `/var/lib/gamecult/bifrost/persona-feedback/persona-feedback-ed25519.seed`) and is checked for regular-file type, exact Unix mode, and service-user ownership every time it signs. VoidBot publishes pending observations only; it neither pumps admission nor receives the signing path.

View file

@ -55,6 +55,8 @@ Required witness path for the hosted service:
Bifrost publishes under these CultMesh namespaces:
The operator bridge additionally advertises `commands/epiphany-operator` and `receipts/epiphany-operator`. The command endpoint consumes strict VoidBot inbox documents and emits dedicated Bifrost-signed admissions; the receipt endpoint exposes only Epiphany-origin, command-bound results. The transport adapter is intentionally unresolved until a live authenticated CultNet RUDP request/reply implementation is installed on Yggdrasil.
- `gamecult.bifrost.service` for service registration, health, build/version,
schema catalog, and command capability discovery.
- `gamecult.bifrost.governance` for motions, topics, comments, votes,

View file

@ -22,6 +22,8 @@ public sealed class ProviderAdvertisementTests
AssertEndpoint(root, "crossing-receipts", "bifrost.crossing_receipt.v1", "bridge");
AssertEndpoint(root, "patron-support-intake", "bifrost.patron_support_event.v0", "patronage");
AssertEndpoint(root, "github-webhooks", "bifrost.work_item.v0", "github");
AssertEndpoint(root, "epiphany-operator-requests", "voidbot.discord.epiphany_operator_request.v0", "operator");
AssertEndpoint(root, "epiphany-operator-results", "epiphany.operator_command.result.v0", "epiphany");
AssertSchemaPurpose(root, "bifrost.crossing_receipt.v1", "canonical Bifrost-owned public crossing receipt");
AssertSchemaPurpose(root, "bifrost.patron_support_event.v0", "current hosted Heimdall-signed patron support fact consumed by Bifrost");
AssertWitness(root, ".bifrost/bridge-receipts.cc", "bifrost.crossing_receipt.v1", "current");
@ -33,6 +35,8 @@ public sealed class ProviderAdvertisementTests
AssertBoundaryForbiddenAuthority(root, "bridge", "surface-specific receipts cannot decide crossing completion");
AssertBoundaryCommand(root, "patron", "consume Heimdall-signed Patreon and PayPal support facts");
AssertBoundaryForbiddenAuthority(root, "patron", "does not store Patreon or PayPal provider tokens");
AssertBoundaryCommand(root, "epiphany-operator", "sign Rust-compatible expiring operator admissions with a dedicated Ed25519 identity");
AssertBoundaryForbiddenAuthority(root, "epiphany-operator", "does not synthesize successful Epiphany results");
}
[Fact]

View file

@ -0,0 +1,14 @@
import test from "node:test";import assert from "node:assert/strict";import {mkdtemp} from "node:fs/promises";import {tmpdir} from "node:os";import {resolve} from "node:path";import {createRequire} from "node:module";
import {admitOperatorRequest,enrollOperatorIdentity,ImmutableOperatorLedger,OPERATOR_SIGNING_PURPOSE} from "../tools/epiphany-operator-commands.mjs";
import {admissionTuple,packetTuple,parseOperatorRequest} from "../tools/epiphany-operator-command-documents.mjs";
import {verifyPurpose} from "../tools/bifrost-signed-identity.mjs";
const requireCult=createRequire(resolve("E:/Projects/CultLib/packages/cultnet-ts/package.json")),{encode}=requireCult("@msgpack/msgpack");
const actorLink={guildId:"guild",discordAuthorId:"owner",bifrostActorId:"actor",heimdallActorRef:"heimdall:actor:1"};
function request(command={kind:"status"}){return {schemaName:"voidbot.discord.epiphany_operator_request",schemaVersion:"voidbot.discord.epiphany_operator_request.v0",requestId:"interaction-1",commandId:"interaction-1",nonce:"interaction-1",sourceEventId:"interaction-1",sourceActorDiscordId:"owner",discordGuildId:"guild",discordChannelId:"channel",discordMessageId:"interaction-1",targetRuntimeId:"epiphany-yggdrasil",issuedAt:"2026-07-19T10:00:00.000Z",expiresAt:"2026-07-19T10:00:30.000Z",producerId:"voidbot",producerRuntimeId:"voidbot-yggdrasil",authorityClass:"operator_request_only",status:"pending",command};}
function result(admission){return {providerIdentityId:"epiphany-host",result:{schemaVersion:"epiphany.operator_command.result.v0",resultId:"result-1",commandId:admission.packet.commandId,packetSha256:admission.packetSha256,targetRuntimeId:admission.packet.targetRuntimeId,disposition:"observed",consequenceKind:"operator-snapshot",consequenceRef:"snapshot-1",completedAt:"2026-07-19T10:00:01.000Z",privateStateExposed:false,operatorStatus:"sleeping",stateStatus:"ready",coordinatorAction:"",brakeStatus:"engaged",detail:"bounded"}};}
async function identity(){const d=await mkdtemp(resolve(tmpdir(),"bifrost-operator-"));return enrollOperatorIdentity(resolve(d,"operator.seed"));}
test("signs the exact Rust packet and admission tuples",async()=>{const id=await identity(),out=await admitOperatorRequest({request:request(),ownerDiscordId:"owner",actorLink,identity:id,encode,expectedEpiphanyIdentityId:"epiphany-host",now:new Date("2026-07-19T10:00:01Z"),transport:{execute:async a=>result(a)}});assert.equal(out.admission.packetSha256,`sha256-${(await import("node:crypto")).createHash("sha256").update(encode(packetTuple(out.admission.packet))).digest("hex")}`);assert.ok(verifyPurpose(id.publicKey,OPERATOR_SIGNING_PURPOSE,encode(admissionTuple(out.admission)),out.admission.providerSignature));assert.equal(out.result.disposition,"observed");});
test("requires exact owner and linked Heimdall actor",async()=>{const id=await identity();for(const args of [{ownerDiscordId:"other",actorLink},{ownerDiscordId:"owner",actorLink:null},{ownerDiscordId:"owner",actorLink:{...actorLink,heimdallActorRef:""}}])await assert.rejects(()=>admitOperatorRequest({request:request(),identity:id,encode,expectedEpiphanyIdentityId:"epiphany-host",now:new Date("2026-07-19T10:00:01Z"),transport:{execute:async a=>result(a)},...args}));});
test("strict request, ttl, command bounds, and expiry fail closed",()=>{assert.throws(()=>parseOperatorRequest({...request(),extra:true}));assert.throws(()=>parseOperatorRequest({...request(),expiresAt:"2026-07-19T10:01:01Z"}));assert.throws(()=>parseOperatorRequest(request({kind:"argv",args:["whoami"]})));assert.throws(()=>parseOperatorRequest(request({kind:"sleep",reason:""})));});
test("does not synthesize success without an authenticated transport result",async()=>{const id=await identity(),base={request:request(),ownerDiscordId:"owner",actorLink,identity:id,encode,expectedEpiphanyIdentityId:"epiphany-host",now:new Date("2026-07-19T10:00:01Z")};await assert.rejects(()=>admitOperatorRequest(base),/live CultNet RUDP adapter is missing/);await assert.rejects(()=>admitOperatorRequest({...base,transport:{execute:async a=>({...result(a),providerIdentityId:"impostor"})}}),/authenticated Epiphany/);await assert.rejects(()=>admitOperatorRequest({...base,transport:{execute:async a=>({...result(a),result:{...result(a).result,packetSha256:"sha256-wrong"}})}}),/not bound/);});
test("immutable ledger rejects request and result collisions",()=>{const ledger=new ImmutableOperatorLedger(),r=request(),a={x:1};ledger.admit(r.requestId,r,a);assert.throws(()=>ledger.admit(r.requestId,{...r,nonce:"different"},a),/collision/);ledger.complete(r.requestId,{x:1},"epiphany");assert.throws(()=>ledger.complete(r.requestId,{x:2},"epiphany"),/collision/);});

View file

@ -0,0 +1,14 @@
import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, sign as ed25519Sign, verify as ed25519Verify } from "node:crypto";
import { chmod, mkdir, open, readFile, stat } from "node:fs/promises";
import { dirname } from "node:path";
const ID_DOMAIN=Buffer.from("epiphany.host-incarnation.identity.v0\0","utf8");
const SIGNATURE_DOMAIN=Buffer.from("epiphany.host-incarnation.signature.v0\0","utf8");
export async function enrollSignedIdentity(path,label="Bifrost identity") { await mkdir(dirname(path),{recursive:true}); const {privateKey}=generateKeyPairSync("ed25519"),seed=Buffer.from(privateKey.export({format:"jwk"}).d,"base64url"),handle=await open(path,"wx",0o600); try{await handle.writeFile(seed);await handle.sync();}finally{await handle.close();} await chmod(path,0o600); return loadSignedIdentity(path,label); }
export async function loadSignedIdentity(path,label="Bifrost identity") { const info=await stat(path); if(!info.isFile())throw new Error(`${label} must be a regular file.`); if(process.platform!=="win32"&&(info.mode&0o077)!==0)throw new Error(`${label} private key must have mode 0600.`); if(process.platform!=="win32"&&typeof process.geteuid==="function"&&info.uid!==process.geteuid())throw new Error(`${label} private key must be owned by the service user.`); const seed=await readFile(path);if(seed.length!==32)throw new Error(`${label} seed must be exactly 32 bytes.`); const key=createPrivateKey({key:Buffer.concat([Buffer.from("302e020100300506032b657004220420","hex"),seed]),format:"der",type:"pkcs8"}),publicKey=createPublicKey(key).export({format:"der",type:"spki"}).subarray(-32),identityId=createHash("sha256").update(ID_DOMAIN).update(publicKey).digest("hex"); return {identityId,publicKey,key}; }
export function signPurpose(identity,purpose,payload){return ed25519Sign(null,domainMessage(purpose,payload),identity.key);}
export function verifyPurpose(publicKey,purpose,payload,signature){const key=createPublicKey({key:Buffer.concat([Buffer.from("302a300506032b6570032100","hex"),Buffer.from(publicKey)]),format:"der",type:"spki"});return ed25519Verify(null,domainMessage(purpose,payload),key,signature);}
export function trustAnchor(identity,createdAt){return {schemaVersion:"epiphany.host_identity_trust_anchor.v0",identityId:identity.identityId,publicKey:new Uint8Array(identity.publicKey),assurance:"os_service_file_bound",identityCreatedAt:createdAt,sourceIdentityRecordSha256:`sha256-${createHash("sha256").update(identity.publicKey).digest("hex")}`};}
function domainMessage(purpose,payload){const p=Buffer.from(purpose,"utf8");return Buffer.concat([SIGNATURE_DOMAIN,u64(p.length),p,u64(payload.length),payload]);}
function u64(value){const b=Buffer.alloc(8);b.writeBigUInt64BE(BigInt(value));return b;}

View file

@ -0,0 +1,20 @@
export const operatorRequestType="voidbot.discord.epiphany_operator_request";
export const operatorRequestSchema="voidbot.discord.epiphany_operator_request.v0";
export const operatorAdmissionType="bifrost.operator_command.delivery";
export const operatorAdmissionSchema="bifrost.operator_command.delivery.v0";
export const operatorResultType="epiphany.operator_command.result";
export const operatorResultSchema="epiphany.operator_command.result.v0";
export function buildOperatorDefinitions(defineDocumentType){const make=(type,schemaId,name,parse)=>defineDocumentType({type,schemaName:type,schemaId,schemaVersion:schemaId,contentHash:schemaId,global:false,name,schema:{parse}});return {request:make(operatorRequestType,operatorRequestSchema,"requestId",parseOperatorRequest),admission:make(operatorAdmissionType,operatorAdmissionSchema,"admissionId",parseOperatorAdmission),result:make(operatorResultType,operatorResultSchema,"resultId",parseOperatorResult)};}
export function parseOperatorRequest(value){const v=exactRecord(value,["schemaName","schemaVersion","requestId","commandId","nonce","sourceEventId","sourceActorDiscordId","discordGuildId","discordChannelId","discordMessageId","targetRuntimeId","issuedAt","expiresAt","producerId","producerRuntimeId","authorityClass","status","command"],"operator request"); exact(v.schemaName,operatorRequestType,"schemaName");exact(v.schemaVersion,operatorRequestSchema,"schemaVersion"); for(const k of ["requestId","commandId","nonce","sourceEventId","sourceActorDiscordId","discordGuildId","discordChannelId","discordMessageId","targetRuntimeId","producerRuntimeId"])text(v[k],k); exact(v.producerId,"voidbot","producerId");exact(v.authorityClass,"operator_request_only","authorityClass");exact(v.status,"pending","status"); const issued=date(v.issuedAt,"issuedAt"),expires=date(v.expiresAt,"expiresAt");if(expires<=issued||expires-issued>60000)throw new Error("operator request TTL must be positive and at most 60 seconds.");parseCommand(v.command);return v;}
export function parseOperatorAdmission(v){v=exactRecord(v,["schemaName","schemaVersion","admissionId","packet","packetSha256","sourceObserverId","sourceObserverRuntimeId","provider","bifrostAdmissionReceiptId","authority","providerIdentityId","providerSignature"],"operator admission");exact(v.schemaName,operatorAdmissionType,"schemaName");exact(v.schemaVersion,operatorAdmissionSchema,"schemaVersion");for(const k of ["admissionId","packetSha256","sourceObserverRuntimeId","bifrostAdmissionReceiptId","providerIdentityId"])text(v[k],k);exact(v.sourceObserverId,"voidbot","sourceObserverId");exact(v.provider,"bifrost","provider");exact(v.authority,"exact_operator_command_only","authority");if(!(v.providerSignature instanceof Uint8Array)||v.providerSignature.length!==64)throw new Error("providerSignature must be 64 bytes.");parsePacket(v.packet);return v;}
export function parseOperatorResult(v){v=exactRecord(v,["schemaVersion","resultId","commandId","packetSha256","targetRuntimeId","disposition","consequenceKind","consequenceRef","completedAt","privateStateExposed","operatorStatus","stateStatus","coordinatorAction","brakeStatus","detail"],"operator result");exact(v.schemaVersion,operatorResultSchema,"schemaVersion");for(const k of ["resultId","commandId","packetSha256","targetRuntimeId","disposition","consequenceKind","completedAt"])text(v[k],k);if(!["observed","applied","refused"].includes(v.disposition))throw new Error("result disposition is invalid.");if(v.privateStateExposed!==false)throw new Error("operator result must not expose private state.");for(const k of ["consequenceRef","operatorStatus","stateStatus","coordinatorAction","brakeStatus","detail"])if(typeof v[k]!=="string")throw new Error(`${k} must be a string.`);date(v.completedAt,"completedAt");return v;}
export function packetTuple(p){return [p.commandId,p.nonce,p.sourceEventId,p.sourceActorId,p.discordGuildId,p.discordChannelId,p.discordMessageId,p.targetRuntimeId,p.issuedAt,p.expiresAt,commandTuple(p.command)];}
export function admissionTuple(a){return [a.schemaVersion,a.admissionId,packetTuple(a.packet),a.packetSha256,a.sourceObserverId,a.sourceObserverRuntimeId,a.provider,a.bifrostAdmissionReceiptId,a.authority,a.providerIdentityId];}
function parsePacket(v){v=exactRecord(v,["commandId","nonce","sourceEventId","sourceActorId","discordGuildId","discordChannelId","discordMessageId","targetRuntimeId","issuedAt","expiresAt","command"],"operator packet");for(const k of ["commandId","nonce","sourceEventId","sourceActorId","discordGuildId","discordChannelId","discordMessageId","targetRuntimeId"])text(v[k],k);date(v.issuedAt,"issuedAt");date(v.expiresAt,"expiresAt");parseCommand(v.command);return v;}
function parseCommand(v){if(!v||typeof v!=="object"||Array.isArray(v))throw new Error("command must be an object.");if(v.kind==="status"||v.kind==="wake")return exactRecord(v,["kind"],"command");if(v.kind==="sleep"){exactRecord(v,["kind","reason"],"command");text(v.reason,"reason");if(v.reason.length>512)throw new Error("sleep reason exceeds 512 characters.");return v;}if(v.kind==="directive"){exactRecord(v,["kind","objective"],"command");text(v.objective,"objective");if(v.objective.length>4096)throw new Error("directive objective exceeds 4096 characters.");return v;}throw new Error("command kind is invalid.");}
function commandTuple(v){if(v.kind==="sleep")return {kind:"sleep",reason:v.reason};if(v.kind==="directive")return {kind:"directive",objective:v.objective};return {kind:v.kind};}
function exactRecord(v,keys,label){if(!v||typeof v!=="object"||Array.isArray(v))throw new Error(`${label} must be an object.`);const actual=Object.keys(v).sort(),expected=[...keys].sort();if(actual.join("\0")!==expected.join("\0"))throw new Error(`${label} has unknown or missing fields.`);return v;}
function text(v,label){if(typeof v!=="string"||!v.trim())throw new Error(`${label} is required.`);return v;}
function exact(v,e,label){if(v!==e)throw new Error(`${label} must be ${e}.`);}
function date(v,label){text(v,label);const n=Date.parse(v);if(!Number.isFinite(n)||!/T/.test(v))throw new Error(`${label} must be RFC3339.`);return n;}

View file

@ -0,0 +1,34 @@
import { createHash } from "node:crypto";
import { resolve } from "node:path";
import { admissionTuple, operatorAdmissionSchema, parseOperatorAdmission, parseOperatorRequest, parseOperatorResult, packetTuple } from "./epiphany-operator-command-documents.mjs";
import { enrollSignedIdentity, loadSignedIdentity, signPurpose, trustAnchor } from "./bifrost-signed-identity.mjs";
export const OPERATOR_SIGNING_PURPOSE="bifrost.operator-command.delivery.v0";
export const defaultOperatorIdentityPath=()=>process.env.BIFROST_EPIPHANY_OPERATOR_PRIVATE_KEY??(process.platform==="win32"?resolve(".bifrost","private","epiphany-operator-ed25519.seed"):"/var/lib/gamecult/bifrost/epiphany-operator/operator-ed25519.seed");
// The transport is deliberately a narrow port. The live CultNet RUDP request/reply
// adapter is not yet present in Bifrost; callers must inject an authenticated port.
export async function admitOperatorRequest({request,ownerDiscordId,actorLink,identity,encode,transport,expectedEpiphanyIdentityId,now=new Date(),ledger=new ImmutableOperatorLedger()}){
parseOperatorRequest(request);
if(request.sourceActorDiscordId!==ownerDiscordId)throw new Error("Epiphany operator command requires the exact configured Discord owner.");
if(!actorLink||actorLink.guildId!==request.discordGuildId||actorLink.discordAuthorId!==request.sourceActorDiscordId||!actorLink.bifrostActorId?.trim()||!actorLink.heimdallActorRef?.trim())throw new Error("Epiphany operator command requires the exact existing Bifrost actor link and Heimdall actor reference.");
const at=now instanceof Date?now:new Date(now);if(!Number.isFinite(at.getTime())||Date.parse(request.issuedAt)>at.getTime()||Date.parse(request.expiresAt)<at.getTime())throw new Error("Epiphany operator request is not currently valid.");
const packet={commandId:request.commandId,nonce:request.nonce,sourceEventId:request.sourceEventId,sourceActorId:actorLink.bifrostActorId,discordGuildId:request.discordGuildId,discordChannelId:request.discordChannelId,discordMessageId:request.discordMessageId,targetRuntimeId:request.targetRuntimeId,issuedAt:request.issuedAt,expiresAt:request.expiresAt,command:request.command};
const packetSha256=`sha256-${createHash("sha256").update(encode(packetTuple(packet))).digest("hex")}`;
const admission={schemaName:"bifrost.operator_command.delivery",schemaVersion:operatorAdmissionSchema,admissionId:request.requestId,packet,packetSha256,sourceObserverId:"voidbot",sourceObserverRuntimeId:request.producerRuntimeId,provider:"bifrost",bifrostAdmissionReceiptId:request.requestId,authority:"exact_operator_command_only",providerIdentityId:identity.identityId,providerSignature:new Uint8Array()};
admission.providerSignature=new Uint8Array(signPurpose(identity,OPERATOR_SIGNING_PURPOSE,encode(admissionTuple(admission))));parseOperatorAdmission(admission);
ledger.admit(request.requestId,request,admission);
if(!transport||typeof transport.execute!=="function")throw new Error("No Epiphany operator transport port is configured; the live CultNet RUDP adapter is missing.");
const envelope=await transport.execute(admission);
if(!envelope||envelope.providerIdentityId!==expectedEpiphanyIdentityId)throw new Error("Operator result did not cross the configured authenticated Epiphany transport boundary.");
const result=parseOperatorResult(envelope.result);
if(result.commandId!==packet.commandId||result.packetSha256!==packetSha256||result.targetRuntimeId!==packet.targetRuntimeId)throw new Error("Epiphany operator result is not bound to the admitted command.");
ledger.complete(request.requestId,result,envelope.providerIdentityId);
return {admission,result,resultProviderIdentityId:envelope.providerIdentityId};
}
export class ImmutableOperatorLedger{#rows=new Map();admit(id,request,admission){const value={request,admission};const old=this.#rows.get(id);if(old&&stable(old)!==stable(value))throw new Error("operator request identity collision");if(!old)this.#rows.set(id,value);}complete(id,result,providerIdentityId){const row=this.#rows.get(id);if(!row)throw new Error("operator result has no admitted request");const next={...row,result,providerIdentityId};if(row.result&&stable(row)!==stable(next))throw new Error("operator result identity collision");this.#rows.set(id,next);}get(id){return this.#rows.get(id);}}
export async function enrollOperatorIdentity(path=defaultOperatorIdentityPath()){return enrollSignedIdentity(path,"Bifrost Epiphany operator identity");}
export async function loadOperatorIdentity(path=defaultOperatorIdentityPath()){return loadSignedIdentity(path,"Bifrost Epiphany operator identity");}
export {trustAnchor};
function stable(v){return JSON.stringify(v,(_k,x)=>x instanceof Uint8Array?Buffer.from(x).toString("hex"):x);}

View file

@ -165,6 +165,9 @@ const locatedService = `${rootVerse}.${currentMachine}.bifrost`;
const plannedLocatedService = `${rootVerse}.yggdrasil.bifrost`;
const motionSurfaceCultMeshUri = `cultmesh://${locatedService}/eve/governance/surface`;
const motionCommandCultMeshUri = `cultmesh://${locatedService}/commands/motion`;
const epiphanyOperatorRequestSchemaId = "voidbot.discord.epiphany_operator_request.v0";
const epiphanyOperatorAdmissionSchemaId = "bifrost.operator_command.delivery.v0";
const epiphanyOperatorResultSchemaId = "epiphany.operator_command.result.v0";
const advertisementDefinition = defineDocumentType({
type: documentType,
@ -330,6 +333,8 @@ function buildAdvertisement(options) {
endpoint("github-webhooks", `${locatedService}/github/webhooks`, "bifrost.work_item.v0", ["github", "work-sync", "review-sync"]),
endpoint("motion-surface", motionSurfaceCultMeshUri, "gamecult.eve.surface.v1", ["cultmesh", "governance", "motion"]),
endpoint("motion-commands", motionCommandCultMeshUri, "bifrost.motion_command.v0", ["command", "cultmesh", "governance", "motion"]),
endpoint("epiphany-operator-requests", `${locatedService}/commands/epiphany-operator`, epiphanyOperatorRequestSchemaId, ["command", "discord", "operator", "cultmesh"]),
endpoint("epiphany-operator-results", `${locatedService}/receipts/epiphany-operator`, epiphanyOperatorResultSchemaId, ["receipt", "epiphany", "operator", "cultmesh"]),
],
routes: [
route("cultmesh-store", ".bifrost/provider-store.cc", "local-cultmesh", true),
@ -369,6 +374,9 @@ function buildAdvertisement(options) {
schema(discordPostReceiptSchemaId, discordPostReceiptDocumentType, "CultMesh receipt for Bifrost-owned Discord persona/bot posts"),
schema("bifrost.patron_support_event.v0", "bifrost.patron-support-event", "current hosted Heimdall-signed patron support fact consumed by Bifrost"),
schema("bifrost.member_capability_snapshot.v0", "bifrost.member-capability-snapshot", "planned Heimdall-consumed membership capability witness"),
schema(epiphanyOperatorRequestSchemaId, "voidbot.discord.epiphany_operator_request", "strict owner-authored Discord operator request inbox"),
schema(epiphanyOperatorAdmissionSchemaId, "bifrost.operator_command.delivery", "Bifrost-signed expiring Epiphany operator admission"),
schema(epiphanyOperatorResultSchemaId, "epiphany.operator_command.result", "Epiphany-origin command result returned across the authenticated transport boundary"),
],
witnesses: [
witness(".bifrost/provider-store.cc", `${schemaId}; ${surfaceSchemaId}; ${interfaceBindingSchemaId}; ${discordPostCommandSchemaId}; ${discordPostReceiptSchemaId}`, "current", "typed provider advertisement, operator surface, Eve interface binding, and Bifrost Discord command request/receipt store"),
@ -380,6 +388,7 @@ function buildAdvertisement(options) {
witness(".bifrost/member-capabilities.cc", "bifrost.member_capability_snapshot.v0", "planned-export", "membership and account capability snapshots consumed by Bifrost"),
witness(".bifrost/bridge-receipts.cc", crossingReceiptSchemaId, "current", "canonical governed public crossing receipt history"),
witness(".bifrost/eve-surfaces.cc", "gamecult.eve.surface.v1", "planned-export", "future product Eve/CultUI surfaces not yet in the provider store"),
witness(".bifrost/epiphany-operator-requests.cc", epiphanyOperatorRequestSchemaId, "configured", "dedicated VoidBot-writable Bifrost operator request inbox; never an Epiphany store"),
],
surfaces: [
surface("bifrost", "Bifrost Operator Dashboard", "gamecult.bifrost.surface.operator", "gamecult.eve.surface_state.v1", ".bifrost/provider-store.cc", "eve", [
@ -489,6 +498,17 @@ function buildAdvertisement(options) {
"surface-specific receipts cannot decide crossing completion",
"does not touch secrets in this advertisement",
]),
boundary("epiphany-operator", "Bifrost admission; Epiphany execution", [
"admit exact owner-only status, sleep, wake, and directive requests",
"require an existing Bifrost actor link and Heimdall actor reference",
"sign Rust-compatible expiring operator admissions with a dedicated Ed25519 identity",
"relay only Epiphany-origin results bound to the admitted command",
], [
"does not accept arbitrary argv or shell commands",
"does not mount or mutate Epiphany stores",
"does not synthesize successful Epiphany results",
"ordinary Persona feedback remains feedback_only",
]),
],
styleCapabilities: [
style("density", ["compact TUI grids", "operator scan tables", "member-facing board/list/detail lowerings"]),