Validate every singleton Domain Store row
This commit is contained in:
parent
e4d9cc2dc9
commit
1fd87ba1bd
4 changed files with 36 additions and 11 deletions
|
|
@ -167,13 +167,7 @@ impl HostCoordinatorDomainStore {
|
||||||
pub fn snapshot(&self) -> Result<DomainStore, DomainStoreError> {
|
pub fn snapshot(&self) -> Result<DomainStore, DomainStoreError> {
|
||||||
self.validate_metadata()?;
|
self.validate_metadata()?;
|
||||||
let read = self.database.begin_read()?;
|
let read = self.database.begin_read()?;
|
||||||
let domain = {
|
let domain = read_domain(&read.open_table(DOMAINS)?)?;
|
||||||
let table = read.open_table(DOMAINS)?;
|
|
||||||
let value = table
|
|
||||||
.get(SINGLETON_KEY)?
|
|
||||||
.ok_or(DomainStoreError::MissingRow("Domain"))?;
|
|
||||||
decode(value.value())?
|
|
||||||
};
|
|
||||||
let generations = read_generations(&read.open_table(GENERATIONS)?)?;
|
let generations = read_generations(&read.open_table(GENERATIONS)?)?;
|
||||||
let participants = read_participants(&read.open_table(PARTICIPANTS)?)?;
|
let participants = read_participants(&read.open_table(PARTICIPANTS)?)?;
|
||||||
let sessions = read_sessions(&read.open_table(SESSIONS)?)?;
|
let sessions = read_sessions(&read.open_table(SESSIONS)?)?;
|
||||||
|
|
@ -309,6 +303,21 @@ fn read_generations(
|
||||||
Ok(records)
|
Ok(records)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_domain(table: &redb::ReadOnlyTable<u8, &[u8]>) -> Result<Domain, DomainStoreError> {
|
||||||
|
let mut domain = None;
|
||||||
|
for entry in table.iter()? {
|
||||||
|
let (key, value) = entry?;
|
||||||
|
let record: Domain = decode(value.value())?;
|
||||||
|
if key.value() != SINGLETON_KEY {
|
||||||
|
return Err(DomainStoreError::InvalidRow("Domain key"));
|
||||||
|
}
|
||||||
|
if domain.replace(record).is_some() {
|
||||||
|
return Err(DomainStoreError::DuplicateRow("Domain"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
domain.ok_or(DomainStoreError::MissingRow("Domain"))
|
||||||
|
}
|
||||||
|
|
||||||
fn read_participants(
|
fn read_participants(
|
||||||
table: &redb::ReadOnlyTable<&[u8; 9], &[u8]>,
|
table: &redb::ReadOnlyTable<&[u8; 9], &[u8]>,
|
||||||
) -> Result<Vec<Participant>, DomainStoreError> {
|
) -> Result<Vec<Participant>, DomainStoreError> {
|
||||||
|
|
|
||||||
|
|
@ -248,6 +248,22 @@ fn metadata_and_typed_row_validation_fail_closed() {
|
||||||
HostCoordinatorDomainStore::open(lock, &row_directory.path("domain.redb"), &domain()),
|
HostCoordinatorDomainStore::open(lock, &row_directory.path("domain.redb"), &domain()),
|
||||||
Err(DomainStoreError::InvalidRow("Generation key"))
|
Err(DomainStoreError::InvalidRow("Generation key"))
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let domain_directory = TestDirectory::new("extra-domain-row");
|
||||||
|
drop(open_store(&domain_directory));
|
||||||
|
let database = Database::open(domain_directory.path("domain.redb")).unwrap();
|
||||||
|
let write = database.begin_write().unwrap();
|
||||||
|
{
|
||||||
|
let mut table = write.open_table(DOMAINS).unwrap();
|
||||||
|
table.insert(1, &[0xff, 0xff][..]).unwrap();
|
||||||
|
}
|
||||||
|
write.commit().unwrap();
|
||||||
|
drop(database);
|
||||||
|
let lock = try_exclusive_flock(lock_file(&domain_directory.path("domain.lock"))).unwrap();
|
||||||
|
assert!(matches!(
|
||||||
|
HostCoordinatorDomainStore::open(lock, &domain_directory.path("domain.redb"), &domain()),
|
||||||
|
Err(DomainStoreError::Codec(_))
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -999,9 +999,9 @@ match its policy digest, fingerprint, namespace, and committed RS identity.
|
||||||
Intent acquisition is monotonic: optional fields move only absent-to-present,
|
Intent acquisition is monotonic: optional fields move only absent-to-present,
|
||||||
and newly acquired child slots move only `Unassigned` to `Staged` to exact
|
and newly acquired child slots move only `Unassigned` to `Staged` to exact
|
||||||
matching current `Committed`. A current committed owner or supervisor may
|
matching current `Committed`. A current committed owner or supervisor may
|
||||||
enter a newly created dependent-role snapshot directly and is then immutable;
|
enter a newly created dependent-role snapshot directly and is then immutable.
|
||||||
legacy ownership cannot use this rule. Recorded evidence cannot be replaced or
|
Recorded evidence cannot be replaced or cleared. A transition to ReleaseIntent
|
||||||
cleared. A transition to ReleaseIntent preserves all evidence; PreparedLower
|
preserves all evidence; PreparedLower
|
||||||
changes only its lease phase to `ReleaseIntent`. ReleaseIntent becomes Clean
|
changes only its lease phase to `ReleaseIntent`. ReleaseIntent becomes Clean
|
||||||
only after independent absence proof and atomically clears every physical
|
only after independent absence proof and atomically clears every physical
|
||||||
field. A bare Intent may become Clean only after the same sealed runtime-absence
|
field. A bare Intent may become Clean only after the same sealed runtime-absence
|
||||||
|
|
|
||||||
|
|
@ -1030,7 +1030,7 @@ not duplicated here.
|
||||||
| **FR-1** | Lazaret shall derive a stable Domain ID from the selected Agent Profile and canonical Agent Workspace. |
|
| **FR-1** | Lazaret shall derive a stable Domain ID from the selected Agent Profile and canonical Agent Workspace. |
|
||||||
| **FR-3** | Tracked and non-ignored workspace edits and supported Git operations shall propagate bidirectionally between host and sandbox. |
|
| **FR-3** | Tracked and non-ignored workspace edits and supported Git operations shall propagate bidirectionally between host and sandbox. |
|
||||||
| **FR-4** | Selected sandbox paths and Ignored Workspace State shall persist across Domain restarts without copying Lazaret host configuration into the sandbox. |
|
| **FR-4** | Selected sandbox paths and Ignored Workspace State shall persist across Domain restarts without copying Lazaret host configuration into the sandbox. |
|
||||||
| **FR-7** | Product recovery shall inspect external runtime resources independently of owner-process liveness. Host-process action shall require the retained pidfd or `PidfsRecoveryMatch` against staged or committed current `PidfsProcessIdentityV1`; legacy PID/start-only ownership, unequal pidfs authority, boot or namespace ambiguity, and unreadable negative evidence shall refuse without numeric-PID signaling. Staged ownership authorizes only exact cleanup/recovery, never service or adoption. Recovery shall also compare typed namespace, mount, artifact, path, and inode evidence before physical cleanup. |
|
| **FR-7** | Product recovery shall inspect external runtime resources independently of owner-process liveness. Host-process action shall require the retained pidfd or `PidfsRecoveryMatch` against staged or committed current `PidfsProcessIdentityV1`; unequal pidfs authority, boot or namespace ambiguity, and unreadable negative evidence shall refuse without numeric-PID signaling. Staged ownership authorizes only exact cleanup/recovery, never service or adoption. Recovery shall also compare typed namespace, mount, artifact, path, and inode evidence before physical cleanup. |
|
||||||
| **FR-8** | Lazaret shall support interactive terminal sessions without degrading supported host terminal capabilities. |
|
| **FR-8** | Lazaret shall support interactive terminal sessions without degrading supported host terminal capabilities. |
|
||||||
| **FR-9** | Trusted host configuration shall allow the developer to disable Git-ignored path isolation for an Agent Profile. |
|
| **FR-9** | Trusted host configuration shall allow the developer to disable Git-ignored path isolation for an Agent Profile. |
|
||||||
| **FR-11** | The host CLI shall list, inspect, structurally verify, and reset all or selected State Anchors without requiring direct manipulation of Lazaret's storage layout. |
|
| **FR-11** | The host CLI shall list, inspect, structurally verify, and reset all or selected State Anchors without requiring direct manipulation of Lazaret's storage layout. |
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue