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> {
|
||||
self.validate_metadata()?;
|
||||
let read = self.database.begin_read()?;
|
||||
let domain = {
|
||||
let table = read.open_table(DOMAINS)?;
|
||||
let value = table
|
||||
.get(SINGLETON_KEY)?
|
||||
.ok_or(DomainStoreError::MissingRow("Domain"))?;
|
||||
decode(value.value())?
|
||||
};
|
||||
let domain = read_domain(&read.open_table(DOMAINS)?)?;
|
||||
let generations = read_generations(&read.open_table(GENERATIONS)?)?;
|
||||
let participants = read_participants(&read.open_table(PARTICIPANTS)?)?;
|
||||
let sessions = read_sessions(&read.open_table(SESSIONS)?)?;
|
||||
|
|
@ -309,6 +303,21 @@ fn read_generations(
|
|||
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(
|
||||
table: &redb::ReadOnlyTable<&[u8; 9], &[u8]>,
|
||||
) -> 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()),
|
||||
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]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
and newly acquired child slots move only `Unassigned` to `Staged` to exact
|
||||
matching current `Committed`. A current committed owner or supervisor may
|
||||
enter a newly created dependent-role snapshot directly and is then immutable;
|
||||
legacy ownership cannot use this rule. Recorded evidence cannot be replaced or
|
||||
cleared. A transition to ReleaseIntent preserves all evidence; PreparedLower
|
||||
enter a newly created dependent-role snapshot directly and is then immutable.
|
||||
Recorded evidence cannot be replaced or cleared. A transition to ReleaseIntent
|
||||
preserves all evidence; PreparedLower
|
||||
changes only its lease phase to `ReleaseIntent`. ReleaseIntent becomes Clean
|
||||
only after independent absence proof and atomically clears every physical
|
||||
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-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-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-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. |
|
||||
|
|
|
|||
Loading…
Reference in a new issue