This repository has been archived on 2026-10-29. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
Epiphany/epiphany-core/src/packaged_release.rs
2026-08-09 08:27:27 +01:00

86 lines
3.3 KiB
Rust

use crate::open_epiphany_cultmesh_node;
use anyhow::{Context, Result, bail};
use chrono::Utc;
use cultcache_rs::SingleFileMessagePackBackingStore;
use std::path::Path;
pub use epiphany_release_construction::*;
const RELEASE_KEY_PREFIX: &str = "epiphany-local/packaged-release/by-id/";
const RELEASE_HEAD_KEY: &str = "epiphany-local/packaged-release/current";
pub fn publish_epiphany_packaged_release(
store: &Path,
runtime_id: &str,
entry: EpiphanyPackagedReleaseEntry,
) -> Result<EpiphanyPackagedReleaseEntry> {
validate_epiphany_packaged_release(&entry)?;
verify_epiphany_packaged_release_files(&entry)?;
if entry.runtime_id != runtime_id {
bail!("release witness runtime disagrees with target Verse");
}
let node = open_epiphany_cultmesh_node(store, runtime_id.to_string())?;
let identity_key = format!("{RELEASE_KEY_PREFIX}{}", entry.release_id);
let existing = node.get::<EpiphanyPackagedReleaseEntry>(&identity_key)?;
if existing.as_ref().is_some_and(|current| current != &entry) {
bail!("immutable packaged release identity collision");
}
let head = EpiphanyPackagedReleaseHead {
schema_version: EPIPHANY_PACKAGED_RELEASE_HEAD_SCHEMA_VERSION.into(),
runtime_id: runtime_id.into(),
release_id: entry.release_id.clone(),
witness_sha256: epiphany_packaged_release_witness_sha256(&entry)?,
published_at_utc: Utc::now().to_rfc3339(),
};
let prior_head = node
.cache()
.get_envelope::<EpiphanyPackagedReleaseHead>(RELEASE_HEAD_KEY)?;
let mut expected = Vec::new();
if let Some(envelope) = prior_head {
expected.push(envelope);
}
let mut replacements = Vec::new();
if existing.is_none() {
replacements.push(node.cache().prepare_entry(&identity_key, &entry)?.0);
}
replacements.push(node.cache().prepare_entry(RELEASE_HEAD_KEY, &head)?.0);
if !SingleFileMessagePackBackingStore::new(store)
.compare_and_swap_batch(&expected, replacements)?
{
bail!("packaged release publication lost current-head compare-and-swap");
}
Ok(entry)
}
pub fn load_epiphany_packaged_release(
store: &Path,
runtime_id: &str,
release_id: &str,
) -> Result<Option<EpiphanyPackagedReleaseEntry>> {
open_epiphany_cultmesh_node(store, runtime_id.to_string())?
.get(&format!("{RELEASE_KEY_PREFIX}{release_id}"))
}
pub fn load_epiphany_packaged_release_head(
store: &Path,
runtime_id: &str,
) -> Result<Option<EpiphanyPackagedReleaseHead>> {
open_epiphany_cultmesh_node(store, runtime_id.to_string())?.get(RELEASE_HEAD_KEY)
}
pub fn authenticate_epiphany_packaged_release(
store: &Path,
runtime_id: &str,
release_id: &str,
expected_witness_sha256: &str,
) -> Result<EpiphanyPackagedReleaseEntry> {
validate_epiphany_packaged_release_sha256(expected_witness_sha256)?;
let entry = load_epiphany_packaged_release(store, runtime_id, release_id)?
.context("packaged release witness is absent")?;
validate_epiphany_packaged_release(&entry)?;
verify_epiphany_packaged_release_files(&entry)?;
if epiphany_packaged_release_witness_sha256(&entry)? != expected_witness_sha256 {
bail!("packaged release witness digest disagrees with pinned task authority");
}
Ok(entry)
}