Bifrost/tools/bifrost-repository-release-authority.mjs
2026-07-16 18:28:13 +01:00

169 lines
10 KiB
JavaScript

import { createRequire } from "node:module";
import { existsSync } from "node:fs";
import { mkdir } from "node:fs/promises";
import { dirname, resolve } from "node:path";
const repoRoot = resolve(import.meta.dirname, "..");
const projectsRoot = resolve(repoRoot, "..");
const cultLibRoot = resolve(process.env.VOIDBOT_CULTLIB_ROOT || resolve(projectsRoot, "CultLib"));
export const repositoryReleaseAuthorityDocumentType = "bifrost.repository_release_authority";
export const repositoryReleaseAuthoritySchemaId = "bifrost.repository_release_authority.v1";
export const defaultRepositoryReleaseAuthorityStorePath = resolve(repoRoot, ".bifrost", "repository-release-authority.cc");
let cachedRuntime;
let cachedDefinition;
export function repositoryReleaseAuthorityDefinition(defineDocumentTypeInput) {
const defineDocumentType = defineDocumentTypeInput ?? loadCultCacheRuntime().defineDocumentType;
cachedDefinition ??= defineDocumentType({
type: repositoryReleaseAuthorityDocumentType,
schemaId: repositoryReleaseAuthoritySchemaId,
schemaName: repositoryReleaseAuthorityDocumentType,
schemaVersion: repositoryReleaseAuthoritySchemaId,
contentHash: repositoryReleaseAuthoritySchemaId,
global: false,
name: "authorityId",
indexes: { repositoryFullName: "repositoryFullName", upstreamRef: "upstreamRef", commitSha: "commitSha", status: "status" },
schema: { parse: parseRepositoryReleaseAuthority },
members: [
{ slot: 1, memberName: "authorityId", typeName: "string", isName: true },
{ slot: 2, memberName: "commandId", typeName: "string" },
{ slot: 3, memberName: "crossingReceiptId", typeName: "string" },
{ slot: 4, memberName: "repositoryFullName", typeName: "string", indexAlias: "repositoryFullName" },
{ slot: 5, memberName: "upstreamRef", typeName: "string", indexAlias: "upstreamRef" },
{ slot: 6, memberName: "commitSha", typeName: "string", indexAlias: "commitSha" },
{ slot: 7, memberName: "decision", typeName: "string" },
{ slot: 8, memberName: "status", typeName: "string", indexAlias: "status" },
{ slot: 9, memberName: "policyDecisionId", typeName: "string" },
{ slot: 10, memberName: "authorityReference", typeName: "string" },
{ slot: 11, memberName: "actorIdentity", typeName: "string" },
{ slot: 12, memberName: "sourceKind", typeName: "string" },
{ slot: 13, memberName: "sourceId", typeName: "string" },
{ slot: 14, memberName: "epiphanyRunId", typeName: "string" },
{ slot: 15, memberName: "epiphanyLaneId", typeName: "string" },
{ slot: 16, memberName: "epiphanyAgentIdentity", typeName: "string" },
{ slot: 17, memberName: "externalReceiptUrl", typeName: "string" },
{ slot: 18, memberName: "externalReceiptId", typeName: "string" },
{ slot: 19, memberName: "authorizedAt", typeName: "string" },
{ slot: 20, memberName: "expiresAt", typeName: "string" },
{ slot: 21, memberName: "revokedAt", typeName: "string" },
{ slot: 22, memberName: "revocationReason", typeName: "string" },
],
});
return cachedDefinition;
}
export async function openRepositoryReleaseAuthorityStore(storePath = defaultRepositoryReleaseAuthorityStorePath) {
const { CultCache, SingleFileMessagePackBackingStore } = loadCultCacheRuntime();
await mkdir(dirname(storePath), { recursive: true });
const cache = CultCache.builder().withDocumentType(repositoryReleaseAuthorityDefinition()).withGenericStore(new SingleFileMessagePackBackingStore(storePath)).build();
await cache.pullAllBackingStores();
return {
storePath,
async put(document) {
const parsed = parseRepositoryReleaseAuthority(document);
await cache.put(repositoryReleaseAuthorityDefinition(), parsed.authorityId, parsed);
return parsed;
},
get(authorityId) { return cache.get(repositoryReleaseAuthorityDefinition(), authorityId); },
getAll() { return cache.getAll(repositoryReleaseAuthorityDefinition()); },
};
}
export function releaseAuthorityId(repositoryFullName, upstreamRef, commitSha) {
return `release:${canonicalRepository(repositoryFullName)}:${canonicalRef(upstreamRef)}:${canonicalCommitSha(commitSha)}`;
}
export function buildAuthorizedRelease(input) {
const repositoryFullName = canonicalRepository(input.repositoryFullName);
const upstreamRef = canonicalRef(input.upstreamRef);
const commitSha = canonicalCommitSha(input.commitSha);
return parseRepositoryReleaseAuthority({
schemaName: repositoryReleaseAuthorityDocumentType,
schemaVersion: repositoryReleaseAuthoritySchemaId,
authorityId: releaseAuthorityId(repositoryFullName, upstreamRef, commitSha),
commandId: input.commandId,
crossingReceiptId: input.crossingReceiptId,
repositoryFullName,
upstreamRef,
commitSha,
decision: "authorize",
status: "authorized",
policyDecisionId: input.policyDecisionId,
authorityReference: input.authorityReference,
actorIdentity: input.actorIdentity,
sourceKind: input.sourceKind,
sourceId: input.sourceId,
epiphanyRunId: input.epiphanyRunId,
epiphanyLaneId: input.epiphanyLaneId,
epiphanyAgentIdentity: input.epiphanyAgentIdentity,
externalReceiptUrl: input.externalReceiptUrl,
externalReceiptId: input.externalReceiptId,
authorizedAt: input.authorizedAt,
expiresAt: input.expiresAt ?? "",
revokedAt: "",
revocationReason: "",
});
}
export function revokeAuthorizedRelease(current, input) {
const parsed = parseRepositoryReleaseAuthority(current);
if (parsed.status !== "authorized") throw new Error(`Release authority ${parsed.authorityId} is already revoked.`);
return parseRepositoryReleaseAuthority({ ...parsed, status: "revoked", revokedAt: requiredString(input.revokedAt, "revokedAt"), revocationReason: requiredString(input.reason, "revocationReason") });
}
export function canonicalRepository(value) {
const repository = requiredString(value, "repositoryFullName").replace(/^https?:\/\/github\.com\//i, "").replace(/\.git$/i, "").replace(/^\/+|\/+$/g, "");
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository)) throw new Error("repositoryFullName must be a canonical GitHub owner/name.");
return repository;
}
export function canonicalRef(value) {
const ref = requiredString(value, "upstreamRef");
if (!/^refs\/(heads|tags)\/[A-Za-z0-9._\/-]+$/.test(ref) || ref.includes("..") || ref.endsWith("/")) throw new Error("upstreamRef must be a canonical refs/heads/... or refs/tags/... name.");
return ref;
}
export function canonicalCommitSha(value) {
const sha = requiredString(value, "commitSha").toLowerCase();
if (!/^[0-9a-f]{40}$/.test(sha)) throw new Error("commitSha must be an exact 40-character hexadecimal Git commit SHA.");
return sha;
}
function parseRepositoryReleaseAuthority(input) {
if (!input || typeof input !== "object" || Array.isArray(input)) throw new Error("Bifrost repository release authority must be an object.");
const repositoryFullName = canonicalRepository(input.repositoryFullName);
const upstreamRef = canonicalRef(input.upstreamRef);
const commitSha = canonicalCommitSha(input.commitSha);
const document = {
schemaName: repositoryReleaseAuthorityDocumentType,
schemaVersion: repositoryReleaseAuthoritySchemaId,
authorityId: requiredString(input.authorityId, "authorityId"), commandId: requiredString(input.commandId, "commandId"),
crossingReceiptId: requiredString(input.crossingReceiptId, "crossingReceiptId"), repositoryFullName, upstreamRef, commitSha,
decision: requiredString(input.decision, "decision"), status: requiredString(input.status, "status"), policyDecisionId: requiredString(input.policyDecisionId, "policyDecisionId"),
authorityReference: requiredString(input.authorityReference, "authorityReference"), actorIdentity: requiredString(input.actorIdentity, "actorIdentity"),
sourceKind: requiredString(input.sourceKind, "sourceKind"), sourceId: requiredString(input.sourceId, "sourceId"),
epiphanyRunId: requiredString(input.epiphanyRunId, "epiphanyRunId"), epiphanyLaneId: requiredString(input.epiphanyLaneId, "epiphanyLaneId"), epiphanyAgentIdentity: requiredString(input.epiphanyAgentIdentity, "epiphanyAgentIdentity"),
externalReceiptUrl: requiredString(input.externalReceiptUrl, "externalReceiptUrl"), externalReceiptId: requiredString(input.externalReceiptId, "externalReceiptId"),
authorizedAt: requiredString(input.authorizedAt, "authorizedAt"), expiresAt: optionalString(input.expiresAt) ?? "", revokedAt: optionalString(input.revokedAt) ?? "", revocationReason: optionalString(input.revocationReason) ?? "",
};
if (document.authorityId !== releaseAuthorityId(repositoryFullName, upstreamRef, commitSha)) throw new Error("authorityId must be derived from repositoryFullName, upstreamRef, and commitSha.");
if (document.decision !== "authorize") throw new Error("Repository release authority decision must be authorize.");
if (!new Set(["authorized", "revoked"]).has(document.status)) throw new Error(`Repository release authority status "${document.status}" is not valid.`);
if (document.status === "authorized" && (document.revokedAt || document.revocationReason)) throw new Error("Authorized release authority cannot carry revocation state.");
if (document.status === "revoked" && (!document.revokedAt || !document.revocationReason)) throw new Error("Revoked release authority requires revocation time and reason.");
return document;
}
function loadCultCacheRuntime() {
if (cachedRuntime) return cachedRuntime;
const candidates = [resolve(cultLibRoot, "packages", "cultcache-ts", "dist", "index.js"), resolve(projectsRoot, "CultCacheTS", "dist", "index.js")];
const entryPoint = candidates.find(existsSync);
if (!entryPoint) throw new Error(`CultCache TypeScript runtime is unavailable. Tried: ${candidates.join(", ")}`);
cachedRuntime = createRequire(entryPoint)(entryPoint);
return cachedRuntime;
}
function requiredString(value, field) { const result = optionalString(value); if (!result) throw new Error(`Bifrost repository release authority field "${field}" must be a non-empty string.`); return result; }
function optionalString(value) { if (typeof value !== "string") return undefined; const trimmed = value.trim(); return trimmed || undefined; }