Fix Bifrost bridge runtime and fixture env isolation

This commit is contained in:
Meta Vens 2026-06-22 14:03:07 +01:00
commit ba01b78d68
7 changed files with 147 additions and 15 deletions

View file

@ -105,7 +105,7 @@ Each request has:
## CLI
`tools/agent-transport.mjs` is intentionally local and boring. It imports the sibling `CultCacheTS` and `CultNetTS` builds directly, writes a `.cc` store, and can emit/apply CultNet raw snapshots.
`tools/agent-transport.mjs` is intentionally local and boring. It imports the local CultCache/CultNet TypeScript runtimes, preferring the current `CultLib\packages\cultcache-ts` and `CultLib\packages\cultnet-ts` packages while retaining the older sibling `CultCacheTS` / `CultNetTS` checkout paths as compatibility reliquaries. It writes a `.cc` store and can emit/apply CultNet raw snapshots.
Default store:

View file

@ -201,6 +201,25 @@ console.log(JSON.stringify({
Assert.Contains("BIFROST_BRIDGE_BASE_URL and BIFROST_BRIDGE_TOKEN", result.Stderr, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task Agent_transport_fixture_harness_does_not_inherit_live_discord_mirror_env()
{
var storePath = Path.Combine(Path.GetTempPath(), $"bifrost-agent-transport-{Guid.NewGuid():N}.cc");
var result = await RunNodeAsync([
"tools/agent-transport.mjs",
"enqueue",
"--repo", "Bifrost",
"--agent", "nibu",
"--title", "Discord inheritance guard",
"--request", "Do not post fixture noise.",
"--store", storePath,
"--allow-unreceipted-activity", "true",
]);
Assert.NotEqual(0, result.ExitCode);
Assert.Contains("require a Discord mirror", result.Stderr, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task Agent_transport_mutation_allows_explicit_operator_recovery()
{
@ -982,6 +1001,8 @@ exit /b 0
process.StartInfo.Environment["BIFROST_BRIDGE_TOKEN"] = string.Empty;
process.StartInfo.Environment["BIFROST_ALLOW_UNGATED_GITHUB"] = string.Empty;
process.StartInfo.Environment["BIFROST_ALLOW_UNRECEIPTED_ACTIVITY"] = string.Empty;
process.StartInfo.Environment["BIFROST_SKIP_LOCAL_ENV"] = "true";
ClearLiveDiscordMirrorEnvironment(process.StartInfo.Environment);
if (environmentOverrides is not null)
{
foreach (var pair in environmentOverrides)
@ -998,6 +1019,35 @@ exit /b 0
return new ProcessResult(process.ExitCode, stdout, stderr);
}
private static void ClearLiveDiscordMirrorEnvironment(IDictionary<string, string?> environment)
{
string[] exactKeys =
[
"BIFROST_ALLOW_UNMIRRORED_GOVERNANCE",
"BIFROST_DISCORD_BOT_TOKEN",
"BIFROST_DISCORD_CHANNEL_ID",
"BIFROST_DISCORD_PERSONA_AVATAR_URL",
"BIFROST_DISCORD_PERSONA_NAME",
"DISCORD_BIFROST_CHANNEL_ID",
"DISCORD_BOT_TOKEN",
"DISCORD_PERSONA_AVATAR_URL",
"DISCORD_PERSONA_AVATAR_URL_BIFROST",
"DISCORD_PERSONA_WEBHOOK_URL",
];
foreach (var key in exactKeys)
{
environment[key] = string.Empty;
}
foreach (var key in environment.Keys
.Where(key => key.StartsWith("BIFROST_DISCORD_PERSONA_WEBHOOK_URL_", StringComparison.OrdinalIgnoreCase))
.ToArray())
{
environment[key] = string.Empty;
}
}
private static async Task<ProcessResult> RunGitAsync(string[] args, string workingDirectory, IReadOnlyDictionary<string, string?>? environmentOverrides = null)
=> await RunCommandAsync("git", args, workingDirectory, environmentOverrides);

View file

@ -16,8 +16,17 @@ const defaultPersonaName = "Bifrost";
const defaultPersonaAvatarUrl =
"https://raw.githubusercontent.com/GameCult/Bifrost/main/src/Bifrost.Web/wwwroot/img/bifrost-profile.png";
const cultCacheRequire = createRequire(resolve(projectsRoot, "CultCacheTS", "node_modules", "cultcache-ts", "package.json"));
const cultNetRequire = createRequire(resolve(projectsRoot, "CultNetTS", "package.json"));
const cultCachePackagePath = resolveFirstExisting("CultCache TypeScript runtime", [
resolve(projectsRoot, "CultLib", "packages", "cultcache-ts", "package.json"),
resolve(projectsRoot, "CultCacheTS", "package.json"),
resolve(projectsRoot, "CultCacheTS", "node_modules", "cultcache-ts", "package.json"),
]);
const cultNetPackagePath = resolveFirstExisting("CultNet TypeScript runtime", [
resolve(projectsRoot, "CultLib", "packages", "cultnet-ts", "package.json"),
resolve(projectsRoot, "CultNetTS", "package.json"),
]);
const cultCacheRequire = createRequire(cultCachePackagePath);
const cultNetRequire = createRequire(cultNetPackagePath);
const {
CultCache,
@ -27,9 +36,19 @@ const {
const {
CultNetDocumentRegistry,
defineCultNetDocumentBinding,
} = cultNetRequire(resolve(projectsRoot, "CultNetTS", "dist", "index.js"));
} = cultNetRequire("cultnet-ts");
const { encode, decode } = cultNetRequire("@msgpack/msgpack");
function resolveFirstExisting(label, candidates) {
for (const candidate of candidates) {
if (existsSync(candidate)) {
return candidate;
}
}
throw new Error(`${label} is unavailable. Tried: ${candidates.join(", ")}`);
}
const documentType = "bifrost.agent-transport.update-request";
const schemaId = "bifrost.agent-transport.update-request.v0";
const validStatuses = new Set(["queued", "claimed", "completed", "cancelled"]);
@ -81,8 +100,7 @@ const cultNetRegistry = new CultNetDocumentRegistry([
]);
async function main() {
loadLocalEnv(resolve(repoRoot, ".env"));
loadLocalEnv(resolve(projectsRoot, "VoidBot", ".env"));
loadBifrostLocalEnv();
const [command, ...rawArgs] = process.argv.slice(2);
const options = parseArgs(rawArgs);
@ -128,6 +146,15 @@ async function main() {
}
}
function loadBifrostLocalEnv() {
if (process.env.BIFROST_SKIP_LOCAL_ENV === "true") {
return;
}
loadLocalEnv(resolve(repoRoot, ".env"));
loadLocalEnv(resolve(projectsRoot, "VoidBot", ".env"));
}
function isMutatingCommand(command) {
return command === "enqueue"
|| command === "claim"

View file

@ -13,8 +13,7 @@ const repoRoot = resolve(scriptDir, "..");
const projectsRoot = resolve(repoRoot, "..");
async function main() {
loadLocalEnv(resolve(repoRoot, ".env"));
loadLocalEnv(resolve(projectsRoot, "VoidBot", ".env"));
loadBifrostLocalEnv();
const [command, ...rawArgs] = process.argv.slice(2);
const options = parseArgs(rawArgs);
@ -44,6 +43,15 @@ async function main() {
}
}
function loadBifrostLocalEnv() {
if (process.env.BIFROST_SKIP_LOCAL_ENV === "true") {
return;
}
loadLocalEnv(resolve(repoRoot, ".env"));
loadLocalEnv(resolve(projectsRoot, "VoidBot", ".env"));
}
async function createGitHubDraftPr(options) {
ensureGitHubBridgeGate(options);
const repoRoot = resolve(requireOption(options, "repo-root"));

View file

@ -17,8 +17,7 @@ const defaultPersonaAvatarUrl =
"https://raw.githubusercontent.com/GameCult/Bifrost/main/src/Bifrost.Web/wwwroot/img/bifrost-profile.png";
async function main() {
loadLocalEnv(resolve(bifrostRoot, ".env"));
loadLocalEnv(resolve(defaultProjectsRoot, "VoidBot", ".env"));
loadBifrostLocalEnv();
const [command, ...rawArgs] = process.argv.slice(2);
const options = parseArgs(rawArgs);
@ -43,6 +42,15 @@ async function main() {
}
}
function loadBifrostLocalEnv() {
if (process.env.BIFROST_SKIP_LOCAL_ENV === "true") {
return;
}
loadLocalEnv(resolve(bifrostRoot, ".env"));
loadLocalEnv(resolve(defaultProjectsRoot, "VoidBot", ".env"));
}
async function dispatchQueuedRequests(options) {
const max = parseInteger(options.max ?? "1", "max");
const statusDir = resolveOptionPath(options["status-dir"] ?? defaultStatusDir);

View file

@ -18,13 +18,28 @@ const defaultPersonaName = "Bifrost";
const defaultPersonaAvatarUrl =
"https://raw.githubusercontent.com/GameCult/Bifrost/main/src/Bifrost.Web/wwwroot/img/bifrost-profile.png";
const cultCacheRequire = createRequire(resolve(projectsRoot, "CultCacheTS", "node_modules", "cultcache-ts", "package.json"));
const cultCachePackagePath = resolveFirstExisting("CultCache TypeScript runtime", [
resolve(projectsRoot, "CultLib", "packages", "cultcache-ts", "package.json"),
resolve(projectsRoot, "CultCacheTS", "package.json"),
resolve(projectsRoot, "CultCacheTS", "node_modules", "cultcache-ts", "package.json"),
]);
const cultCacheRequire = createRequire(cultCachePackagePath);
const {
CultCache,
SingleFileMessagePackBackingStore,
defineDocumentType,
} = cultCacheRequire("cultcache-ts");
function resolveFirstExisting(label, candidates) {
for (const candidate of candidates) {
if (existsSync(candidate)) {
return candidate;
}
}
throw new Error(`${label} is unavailable. Tried: ${candidates.join(", ")}`);
}
const topicType = "bifrost.governance.topic";
const topicSchemaId = "bifrost.governance.topic.v0";
const commentType = "bifrost.governance.topic-comment";
@ -91,8 +106,7 @@ const commentDefinition = defineDocumentType({
});
async function main() {
loadLocalEnv(resolve(repoRoot, ".env"));
loadLocalEnv(resolve(projectsRoot, "VoidBot", ".env"));
loadBifrostLocalEnv();
const [command, ...rawArgs] = process.argv.slice(2);
const options = parseArgs(rawArgs);
@ -138,6 +152,15 @@ async function main() {
}
}
function loadBifrostLocalEnv() {
if (process.env.BIFROST_SKIP_LOCAL_ENV === "true") {
return;
}
loadLocalEnv(resolve(repoRoot, ".env"));
loadLocalEnv(resolve(projectsRoot, "VoidBot", ".env"));
}
async function openCache(storePath) {
const cache = CultCache.builder()
.withDocumentType(topicDefinition)

View file

@ -11,7 +11,7 @@ const repoRoot = resolve(scriptDir, "..");
const projectsRoot = resolve(repoRoot, "..");
const defaultStorePath = resolve(repoRoot, ".bifrost", "provider-advertisement.cc");
const cultCacheRequire = createRequire(resolve(projectsRoot, "CultCacheTS", "package.json"));
const cultCacheRequire = createRequire(resolveCultCachePackagePath());
const cultCacheRuntime = loadCultCacheRuntime();
const {
CultCache,
@ -21,8 +21,8 @@ const {
function loadCultCacheRuntime() {
const candidates = [
resolve(projectsRoot, "CultCacheTS", "dist", "index.js"),
resolve(projectsRoot, "CultLib", "packages", "cultcache-ts", "dist", "index.js"),
resolve(projectsRoot, "CultCacheTS", "dist", "index.js"),
];
for (const candidate of candidates) {
@ -39,6 +39,22 @@ function loadCultCacheRuntime() {
throw new Error("CultCache TypeScript runtime with document APIs is unavailable.");
}
function resolveCultCachePackagePath() {
const candidates = [
resolve(projectsRoot, "CultLib", "packages", "cultcache-ts", "package.json"),
resolve(projectsRoot, "CultCacheTS", "package.json"),
resolve(projectsRoot, "CultCacheTS", "node_modules", "cultcache-ts", "package.json"),
];
for (const candidate of candidates) {
if (existsSync(candidate)) {
return candidate;
}
}
throw new Error(`CultCache TypeScript runtime is unavailable. Tried: ${candidates.join(", ")}`);
}
const documentType = "gamecult.eve.provider_advertisement";
const schemaId = "gamecult.eve.provider_advertisement.v1";
const documentId = "bifrost";