Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4361bdd
feat(inventory): record host identity evidence
polaz Oct 29, 2026
7b69f7b
feat(inventory): compare host identity
polaz Oct 29, 2026
dc33da8
feat(import): review current address deltas
polaz Oct 29, 2026
8876560
test(observe): check native identity sources
polaz Oct 29, 2026
ca198bd
feat(inventory): reconcile host identities
polaz Oct 29, 2026
ffec1a8
perf(inventory): use existential parent probes
polaz Oct 29, 2026
c04b3da
feat(inventory): record reviewed VM placement
polaz Oct 29, 2026
c3b5c3d
fix(store): restore detached relation edges
polaz Oct 29, 2026
2603293
fix(inventory): validate scope before rebinding
polaz Oct 29, 2026
047f33b
fix(inventory): scope identity candidate limits
polaz Oct 29, 2026
72ee444
fix(import): preserve personal source targets
polaz Oct 29, 2026
e308538
fix(ui): show address stability changes
polaz Oct 29, 2026
7e62fa2
fix(ui): replace retired roots on alias navigation
polaz Oct 29, 2026
ae64e7f
fix(ui): show final identity decisions
polaz Oct 29, 2026
28b378c
fix(inventory): guard correction impact reads
polaz Oct 29, 2026
8edc19c
fix(inventory): preserve stale inheritance
polaz Oct 29, 2026
a9ce33e
test(placement): make scoped captures explicit
polaz Oct 29, 2026
77c3293
fix(ui): invalidate edited placement cursors
polaz Oct 29, 2026
4160122
test(observer): distinguish hardware UUID digests
polaz Oct 29, 2026
54d071e
test(placement): cover reviewed first placement
polaz Oct 29, 2026
52da630
test(store): use nonexclusive parallel relations
polaz Oct 29, 2026
f93b735
perf(inventory): skip empty identity searches
polaz Oct 29, 2026
521e2b0
test(store): qualify correction and source bounds
polaz Oct 30, 2026
fe77bd4
test(store): exclude service from restricted view
polaz Oct 30, 2026
1038b16
fix(inventory): preserve reviewed source bounds
polaz Oct 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/omloom-contracts/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.boxed(".omloom.workspace.v1.Address.allocation")
.boxed(".omloom.workspace.v1.Address.interface")
.boxed(".omloom.workspace.v1.Reply.result.detail")
.boxed(".omloom.workspace.v1.Reply.result.vm_placement_review")
.boxed(".omloom.workspace.v1.HostImport.report")
.file_descriptor_set_path(&descriptor)
.compile_protos(&[workspace], &["../../proto"])?;
Expand Down
1 change: 1 addition & 0 deletions crates/omloom-observe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ publish = false
[dependencies]
omloom-contracts = { path = "../omloom-contracts" }
prost.workspace = true
uuid.workspace = true
serde_json = "1"
sha2 = "0.10"
tokio = { version = "1", features = ["macros", "rt", "process", "io-util", "time"] }
Expand Down
25 changes: 18 additions & 7 deletions crates/omloom-observe/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,15 @@ fn add(report: &mut api::HostReport, value: api::HostFinding) -> Result<()> {

async fn collect() -> Result<api::HostReport> {
let mut runner = command::Runner::new();
let (platform, source, boot, name) = native::identity(&mut runner).await?;
let identity = native::identity(&mut runner).await?;
let native::Identity {
platform,
source,
boot,
name,
hardware,
installation: installation_claim,
} = identity;
let observed_at = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|_| "Clock predates Unix epoch")?
Expand All @@ -91,10 +99,9 @@ async fn collect() -> Result<api::HostReport> {
observed_at,
..Default::default()
};
add(
&mut report,
finding("hardware", wire::AssetKind::Unspecified, None, &name),
)?;
let mut host = finding("hardware", wire::AssetKind::Unspecified, None, &name);
host.identity.extend(hardware);
add(&mut report, host)?;
let mut installation = finding(
"installation",
wire::AssetKind::SoftwareInstallation,
Expand All @@ -105,6 +112,7 @@ async fn collect() -> Result<api::HostReport> {
"installation_kind",
wire::InstallationKind::OperatingSystem.as_str_name(),
));
installation.identity.push(installation_claim);
add(&mut report, installation)?;
coverage(
&mut report,
Expand All @@ -120,8 +128,11 @@ async fn collect() -> Result<api::HostReport> {
});
native::collect(&mut runner, &mut report).await?;
runtime::collect(&mut runner, &mut report).await?;
let (_, after_source, after_boot, _) = native::identity(&mut runner).await?;
check_identity(&source, &boot, &after_source, &after_boot)?;
let after = native::identity(&mut runner).await?;
check_identity(&source, &boot, &after.source, &after.boot)?;
if report.findings.first().and_then(|v| v.identity.first()) != after.hardware.as_ref() {
return Err("Hardware evidence changed during collection; output not written".into());
}
Ok(report)
}

Expand Down
110 changes: 88 additions & 22 deletions crates/omloom-observe/src/native.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use super::command::Runner;
use super::{MAX_FACTS, Result, add, api, coverage, finding, local, wire};
use serde_json::Value;
#[cfg(test)]
mod tests;

pub(super) fn json(input: &str) -> Result<Value> {
// Preflight limits both structural fan-out and stack use before serde builds
Expand Down Expand Up @@ -113,9 +115,46 @@ async fn ps(runner: &mut Runner, script: &str) -> Result<String> {
.await
.map(|v| v.text)
}
pub(super) async fn identity(
runner: &mut Runner,
) -> Result<(&'static str, String, String, String)> {
pub(super) struct Identity {
pub platform: &'static str,
pub source: String,
pub boot: String,
pub name: String,
pub hardware: Option<api::HostIdentityClaim>,
pub installation: api::HostIdentityClaim,
}

fn identity_claim(
kind: api::HostIdentityKind,
namespace: &str,
raw: &str,
) -> Result<api::HostIdentityClaim> {
let raw = raw.trim();
if raw.is_empty() || raw.len() > 128 || raw.chars().any(char::is_control) {
return Err("Invalid native identity claim".into());
}
Ok(api::HostIdentityClaim {
kind: kind as i32,
namespace: namespace.into(),
digest: super::digest(&raw.to_ascii_lowercase()),
quality: api::HostIdentityQuality::Reported as i32,
})
}

fn hardware_claim(namespace: &str, raw: &str) -> Option<api::HostIdentityClaim> {
let uuid = uuid::Uuid::parse_str(raw.trim()).ok()?;
if uuid.is_nil() || uuid == uuid::Uuid::max() {
return None;
}
identity_claim(
api::HostIdentityKind::HardwareUuid,
namespace,
&uuid.to_string(),
)
.ok()
}

pub(super) async fn identity(runner: &mut Runner) -> Result<Identity> {
match std::env::consts::OS {
"macos" => {
let device = runner
Expand All @@ -141,12 +180,18 @@ pub(super) async fn identity(
.run("/usr/sbin/sysctl", &["-n", "hw.model"])
.await?
.text;
Ok((
"macos",
format!("{hardware}/{installation}"),
boot.trim().into(),
model.trim().into(),
))
Ok(Identity {
platform: "macos",
source: format!("{hardware}/{installation}"),
boot: boot.trim().into(),
name: model.trim().into(),
hardware: hardware_claim("apple/platform-uuid", hardware),
installation: identity_claim(
api::HostIdentityKind::InstallationId,
"macos/apfs-volume",
installation,
)?,
})
}
"linux" => {
let source = runner.run("cat", &["/etc/machine-id"]).await?.text;
Expand All @@ -155,22 +200,43 @@ pub(super) async fn identity(
.await?
.text;
let name = runner.run("uname", &["-n"]).await?.text;
Ok((
"linux",
source.trim().into(),
boot.trim().into(),
name.trim().into(),
))
// DMI may be unreadable without root. Missing hardware evidence
// does not prevent collecting the installation's current facts.
let hardware = match runner.run("cat", &["/sys/class/dmi/id/product_uuid"]).await {
Ok(value) => hardware_claim("smbios/system-uuid", &value.text),
Err(_) => None,
};
Ok(Identity {
platform: "linux",
source: source.trim().into(),
boot: boot.trim().into(),
name: name.trim().into(),
hardware,
installation: identity_claim(
api::HostIdentityKind::InstallationId,
"linux/machine-id",
&source,
)?,
})
}
"windows" => {
let result = ps(runner, "$ErrorActionPreference='Stop'; [Console]::OutputEncoding=[Text.UTF8Encoding]::new($false); $os=Get-CimInstance Win32_OperatingSystem; $cs=Get-CimInstance Win32_ComputerSystemProduct; @{source=(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography').MachineGuid; boot=$os.LastBootUpTime.ToUniversalTime().ToString('o'); name=$cs.Name} | ConvertTo-Json -Compress").await?;
let result = ps(runner, "$ErrorActionPreference='Stop'; [Console]::OutputEncoding=[Text.UTF8Encoding]::new($false); $os=Get-CimInstance Win32_OperatingSystem; $cs=Get-CimInstance Win32_ComputerSystemProduct; @{source=(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography').MachineGuid; boot=$os.LastBootUpTime.ToUniversalTime().ToString('o'); name=$cs.Name; hardware=$cs.UUID} | ConvertTo-Json -Compress").await?;
let value = json(&result)?;
Ok((
"windows",
string(&value, "source")?.into(),
string(&value, "boot")?.into(),
string(&value, "name")?.into(),
))
Ok(Identity {
platform: "windows",
source: string(&value, "source")?.into(),
boot: string(&value, "boot")?.into(),
name: string(&value, "name")?.into(),
hardware: value
.get("hardware")
.and_then(Value::as_str)
.and_then(|v| hardware_claim("smbios/system-uuid", v)),
installation: identity_claim(
api::HostIdentityKind::InstallationId,
"windows/machine-guid",
string(&value, "source")?,
)?,
})
}
_ => Err("Native platform is not supported".into()),
}
Expand Down
81 changes: 81 additions & 0 deletions crates/omloom-observe/src/native/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use super::*;

// Exercise the native OS source on each qualified host, without assuming that
// an unreadable or unset hardware UUID exists or inventing one from an address.
#[tokio::test]
async fn native_identity_observation_keeps_layers_separate() -> Result<()> {
let value = identity(&mut Runner::new()).await?;
let namespace = match std::env::consts::OS {
"macos" => "macos/apfs-volume",
"linux" => "linux/machine-id",
"windows" => "windows/machine-guid",
_ => return Err("Unsupported native identity platform".into()),
};
assert!(!value.source.is_empty());
assert!(!value.boot.is_empty());
assert_eq!(value.installation.namespace, namespace);
assert_eq!(
value.installation.kind,
api::HostIdentityKind::InstallationId as i32
);
assert_eq!(value.installation.digest.len(), 64);
if let Some(hardware) = value.hardware {
assert_eq!(hardware.kind, api::HostIdentityKind::HardwareUuid as i32);
assert_eq!(hardware.quality, api::HostIdentityQuality::Reported as i32);
assert_eq!(hardware.digest.len(), 64);
assert_ne!(hardware.namespace, value.installation.namespace);
}
Ok(())
}

// Normalized claims compare across boots, while distinct hardware UUIDs stay distinct.
// A MAC, IP or hostname is never substituted for missing hardware evidence.
#[test]
fn hardware_claims_are_scoped_normalized_and_explicitly_untrusted() {
let first = hardware_claim("smbios/system-uuid", "8D60C19A-489A-40C9-B381-D97E5D568DF8")
.expect("source UUID");
let second = hardware_claim("smbios/system-uuid", "8d60c19a-489a-40c9-b381-d97e5d568df8")
.expect("source UUID");
assert_eq!(first, second);
let other = hardware_claim("smbios/system-uuid", "11111111-2222-4333-8444-555555555555")
.expect("distinct source UUID");
assert_ne!(first.digest, other.digest);
assert_eq!(first.quality, api::HostIdentityQuality::Reported as i32);
assert_eq!(first.digest.len(), 64);
assert!(!first.digest.contains("8d60c19a"));
assert_ne!(first.namespace, "apple/platform-uuid");
for unknown in [
"",
"192.0.2.1",
"To be filled by OEM",
"00000000-0000-0000-0000-000000000000",
"ffffffff-ffff-ffff-ffff-ffffffffffff",
] {
assert!(hardware_claim("smbios/system-uuid", unknown).is_none());
}
}

// An installation ID remains a distinct namespace and layer even when a source
// happens to report the same bytes as its hardware UUID.
#[test]
fn installation_claims_do_not_collapse_into_hardware_identity() {
let raw = "8d60c19a-489a-40c9-b381-d97e5d568df8";
let hardware = hardware_claim("smbios/system-uuid", raw).expect("UUID");
let installation = identity_claim(
api::HostIdentityKind::InstallationId,
"windows/machine-guid",
raw,
)
.expect("installation");
assert_eq!(hardware.digest, installation.digest);
assert_ne!(hardware.namespace, installation.namespace);
assert_ne!(hardware.kind, installation.kind);
assert!(
identity_claim(
api::HostIdentityKind::InstallationId,
"linux/machine-id",
"\n"
)
.is_err()
);
}
4 changes: 3 additions & 1 deletion crates/omloom-store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repository.workspace = true

[features]
default = ["std"]
std = ["dep:coordinode-embed", "dep:coordinode-core", "dep:omloom-contracts", "dep:sha2", "dep:prost", "dep:csv", "dep:ipnet"]
std = ["dep:coordinode-embed", "dep:coordinode-core", "dep:omloom-contracts", "dep:sha2", "dep:prost", "dep:csv", "dep:ipnet", "dep:serde", "dep:serde_json"]
diagnostics = ["std"]

[dependencies]
Expand All @@ -20,6 +20,8 @@ sha2 = { version = "0.10", optional = true }
prost = { workspace = true, optional = true }
csv = { version = "1.4", optional = true }
ipnet = { version = "2.12", optional = true }
serde = { version = "1", features = ["derive"], optional = true }
serde_json = { version = "1", optional = true }

[dev-dependencies]
coordinode-storage = { git = "https://github.com/structured-world/coordinode", rev = "1862962a3da20e5e57d7ab5126e69bd5817d7c61" }
Expand Down
Loading
Loading