Compose selected State Anchor paths over prepared roots

This commit is contained in:
ms2k 2026-07-17 03:58:27 +09:00
commit 4dd2b150d2

View file

@ -23,7 +23,8 @@ use rustix::fs::{
use sha2::{Digest, Sha256};
use crate::layer::{
IdMap, LayoutSource, POLICY_NAME, PreparationLimits, apply_layout, remove_children,
IdMap, LayoutSource, POLICY_NAME, PreparationLimits, PreparedDestination,
PreparedDestinationKind, apply_layout, materialize_destinations, remove_children,
verify_layout,
};
use crate::transport::TransportedImage;
@ -38,6 +39,7 @@ pub struct PrepareRequest {
pub source_identity: Vec<u8>,
pub selected_manifest_digest: [u8; 32],
pub id_map: IdMap,
pub destinations: Vec<PreparedDestination>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@ -115,6 +117,22 @@ impl PreparedLowerStore {
self
}
/// Opens the exact published artifact beneath the retained prepared-lower root.
///
/// # Errors
/// Refuses a non-canonical artifact record, catalog drift, or physical identity drift.
pub fn open_artifact_authority(&self, artifact: &PreparedArtifact) -> Result<File> {
let catalog_artifact = self
.catalog_artifact(artifact.prepared_artifact_id)?
.context("prepared artifact is absent from the catalog")?;
if catalog_artifact != *artifact {
bail!("prepared artifact does not match the catalog");
}
self.require_artifact_identity(artifact)?;
self.open_relative(&artifact_path(artifact.prepared_artifact_id))?
.context("prepared artifact path is absent")
}
/// Materializes and atomically publishes one verified immutable prepared lower.
///
/// # Errors
@ -134,6 +152,7 @@ impl PreparedLowerStore {
operation_id: PrepareOperationId,
image: &TransportedImage,
id_map: IdMap,
destinations: Vec<PreparedDestination>,
) -> Result<PreparedArtifact> {
let layout = image
.retained_layout()
@ -144,6 +163,7 @@ impl PreparedLowerStore {
source_identity: image.source_identity.clone(),
selected_manifest_digest: image.selected_manifest_digest,
id_map,
destinations,
};
self.prepare_from_source(&request, &LayoutSource::Retained(layout.root()))
}
@ -194,6 +214,7 @@ impl PreparedLowerStore {
return Err(error.into());
}
if let Err(error) = apply_layout(layout, &stage, &verified, &request.id_map, self.limits)
.and_then(|()| materialize_destinations(&stage, &request.destinations, &request.id_map))
.and_then(|()| sync_tree(&stage))
{
if self
@ -494,7 +515,13 @@ impl Identities {
fn new(request: &PrepareRequest) -> Result<Self> {
let source = SourceIdentity::new(request.source_identity.clone());
let manifest = SelectedManifestDigest::new(request.selected_manifest_digest);
let policy = PolicyId::new(hash_parts(&[b"lazaret-policy-v1", POLICY_NAME.as_bytes()]));
let destinations = canonical_destinations(&request.destinations)?;
let destination_bytes = destination_policy_bytes(&destinations);
let policy = PolicyId::new(hash_parts(&[
b"lazaret-policy-v2",
POLICY_NAME.as_bytes(),
&destination_bytes,
]));
let id_map_bytes = postcard::to_stdvec(&request.id_map)?;
let id_map = IdMapDigest::new(hash_parts(&[b"lazaret-id-map-v1", &id_map_bytes]));
let artifact_id = PreparedArtifactId::new(hash_parts(&[
@ -555,6 +582,42 @@ impl Identities {
}
}
fn canonical_destinations(
destinations: &[PreparedDestination],
) -> Result<Vec<PreparedDestination>> {
let mut destinations = destinations.to_vec();
for destination in &destinations {
let mut components = destination.path.components();
if components.next() != Some(std::path::Component::RootDir)
|| components.any(|component| !matches!(component, std::path::Component::Normal(_)))
{
bail!("prepared destination is not an absolute normalized path");
}
}
destinations.sort();
destinations.dedup();
for pair in destinations.windows(2) {
if pair[0].path == pair[1].path && pair[0].kind != pair[1].kind {
bail!("prepared destination has conflicting types");
}
}
Ok(destinations)
}
fn destination_policy_bytes(destinations: &[PreparedDestination]) -> Vec<u8> {
let mut bytes = b"lazaret-prepared-layout-policy-v1\0".to_vec();
for destination in destinations {
bytes.extend_from_slice(destination.path.as_os_str().as_bytes());
bytes.push(0);
bytes.push(match destination.kind {
PreparedDestinationKind::Directory => b'd',
PreparedDestinationKind::RegularFile => b'f',
});
bytes.push(0);
}
bytes
}
fn artifact_from_operation(
operation: &PrepareOperation,
path: &Path,