Implement exact State Anchor mount and lease lifecycle
This commit is contained in:
parent
27bc3d835e
commit
e6d6406c64
2 changed files with 1852 additions and 0 deletions
|
|
@ -330,6 +330,32 @@ impl Catalog {
|
|||
/// Returns an error if the artifact is absent or reclaiming, the lease exists, or the
|
||||
/// transaction fails.
|
||||
pub fn acquire_lease(&self, lease: &ArtifactLease) -> Result<(), CatalogError> {
|
||||
self.acquire_lease_with(lease, None)
|
||||
}
|
||||
|
||||
/// Inserts one lease only for an exactly matching available artifact row.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns an error for artifact identity drift, absence, reclaim, lease conflict, or a
|
||||
/// transaction failure.
|
||||
pub fn acquire_lease_checked(
|
||||
&self,
|
||||
expected_artifact: &PreparedArtifact,
|
||||
lease: &ArtifactLease,
|
||||
) -> Result<(), CatalogError> {
|
||||
if expected_artifact.prepared_artifact_id != lease.prepared_artifact_id {
|
||||
return Err(CatalogError::OwnershipMismatch {
|
||||
row: CatalogRow::PreparedArtifact,
|
||||
});
|
||||
}
|
||||
self.acquire_lease_with(lease, Some(expected_artifact))
|
||||
}
|
||||
|
||||
fn acquire_lease_with(
|
||||
&self,
|
||||
lease: &ArtifactLease,
|
||||
expected_artifact: Option<&PreparedArtifact>,
|
||||
) -> Result<(), CatalogError> {
|
||||
self.write(|transaction| {
|
||||
let artifact_key = artifact_key(lease.prepared_artifact_id);
|
||||
let artifacts = transaction
|
||||
|
|
@ -337,6 +363,11 @@ impl Catalog {
|
|||
.map_err(CatalogError::redb)?;
|
||||
let artifact: PreparedArtifact =
|
||||
require_row(&artifacts, &artifact_key, CatalogRow::PreparedArtifact)?;
|
||||
if expected_artifact.is_some_and(|expected| *expected != artifact) {
|
||||
return Err(CatalogError::OwnershipMismatch {
|
||||
row: CatalogRow::PreparedArtifact,
|
||||
});
|
||||
}
|
||||
if artifact.state != ArtifactState::Available {
|
||||
return Err(CatalogError::ArtifactUnavailable);
|
||||
}
|
||||
|
|
@ -359,7 +390,21 @@ impl Catalog {
|
|||
///
|
||||
/// Returns an error when the exact row is absent, forged, or the transaction fails.
|
||||
pub fn release_lease(&self, expected: &ArtifactLease) -> Result<(), CatalogError> {
|
||||
self.release_lease_checked(expected, || Ok(()))
|
||||
}
|
||||
|
||||
/// Removes one exact lease only after caller-owned Domain and mount evidence is rechecked
|
||||
/// while the catalog lock is held.
|
||||
///
|
||||
/// # Errors
|
||||
/// Returns the evidence refusal or any exact-row and transaction failure without removal.
|
||||
pub fn release_lease_checked(
|
||||
&self,
|
||||
expected: &ArtifactLease,
|
||||
verify_release: impl FnOnce() -> Result<(), CatalogError>,
|
||||
) -> Result<(), CatalogError> {
|
||||
self.write(|transaction| {
|
||||
verify_release()?;
|
||||
let key = lease_key(expected);
|
||||
let mut table = transaction.open_table(LEASES).map_err(CatalogError::redb)?;
|
||||
require_exact(&table, &key, expected, CatalogRow::ArtifactLease)?;
|
||||
|
|
|
|||
|
|
@ -174,6 +174,66 @@ fn shared_artifact_waits_for_every_lease() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_evidence_is_checked_while_lease_remains_locked() {
|
||||
let fixture = Fixture::new();
|
||||
let artifact = fixture.publish(31);
|
||||
let exact = lease(&artifact, 1, 3);
|
||||
fixture.catalog.acquire_lease(&exact).unwrap();
|
||||
|
||||
let refused = fixture.catalog.release_lease_checked(&exact, || {
|
||||
Err(CatalogError::Io(std::io::Error::other(
|
||||
"injected release-evidence mismatch",
|
||||
)))
|
||||
});
|
||||
assert!(matches!(refused, Err(CatalogError::Io(_))));
|
||||
assert_eq!(
|
||||
fixture.catalog.snapshot().unwrap().artifact_leases,
|
||||
vec![exact]
|
||||
);
|
||||
|
||||
fixture
|
||||
.catalog
|
||||
.release_lease_checked(&exact, || Ok(()))
|
||||
.unwrap();
|
||||
assert!(
|
||||
fixture
|
||||
.catalog
|
||||
.snapshot()
|
||||
.unwrap()
|
||||
.artifact_leases
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lease_acquisition_binds_the_exact_artifact_row() {
|
||||
let fixture = Fixture::new();
|
||||
let artifact = fixture.publish(32);
|
||||
let exact = lease(&artifact, 2, 4);
|
||||
let mut forged = artifact.clone();
|
||||
forged.inode = InodeId::new(9_999);
|
||||
|
||||
assert!(matches!(
|
||||
fixture.catalog.acquire_lease_checked(&forged, &exact),
|
||||
Err(CatalogError::OwnershipMismatch {
|
||||
row: CatalogRow::PreparedArtifact
|
||||
})
|
||||
));
|
||||
assert!(
|
||||
fixture
|
||||
.catalog
|
||||
.snapshot()
|
||||
.unwrap()
|
||||
.artifact_leases
|
||||
.is_empty()
|
||||
);
|
||||
fixture
|
||||
.catalog
|
||||
.acquire_lease_checked(&artifact, &exact)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_domain_generation_cannot_bind_two_artifacts() {
|
||||
let fixture = Fixture::new();
|
||||
|
|
|
|||
Loading…
Reference in a new issue