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
334eaff
feat: plan racks, cabling and replacements
polaz Nov 1, 2026
fb4a737
test(plans): accept matching post-effect evidence
polaz Nov 1, 2026
a7e0900
test(physical): evaluate recorded power checks
polaz Nov 1, 2026
ddaad0c
test(cabling): reject cross-end step bases
polaz Nov 1, 2026
4947de5
fix(physical): reject occupied move destinations
polaz Nov 1, 2026
0886f7a
fix(plans): preserve reassigned resource locks
polaz Nov 1, 2026
161b0b6
fix(physical): protect reserved component slots
polaz Nov 1, 2026
99daf42
fix(plans): list personal summaries without scope
polaz Nov 1, 2026
19c4413
fix(physical): guard structural position creation
polaz Nov 1, 2026
d90de6a
fix(physical): authorize offer reads as reads
polaz Nov 1, 2026
8dd6092
fix(physical): readmit direct item placements
polaz Nov 1, 2026
689f82e
fix(physical): recheck slot class before binding
polaz Nov 1, 2026
c9cfa54
fix(physical): canonicalize feedback root scopes
polaz Nov 1, 2026
fa831fb
style(physical): clarify lock ownership guard
polaz Nov 1, 2026
5db2ee4
fix(ui): plan chassis changes in enclosing scopes
polaz Nov 1, 2026
a1aeebd
fix(ui): invalidate revision-bound quality pages
polaz Nov 1, 2026
a9bb383
fix(ui): reset plan-specific report state
polaz Nov 1, 2026
27aa433
perf(physical): trim repeated validation work
polaz Nov 1, 2026
2abdc73
test(physical): check fresh power results
polaz Nov 1, 2026
1ef7b40
test(physical): assert retained slot ownership
polaz Nov 1, 2026
3279303
fix(physical): readmit replacement batches
polaz Nov 1, 2026
d6d9554
fix(physical): reject duplicate predecessors
polaz Nov 1, 2026
8715a73
fix(physical): authorize current quality roots
polaz Nov 1, 2026
05e76f9
fix(ui): invalidate quality on scope changes
polaz Nov 1, 2026
ef8020b
docs(store): explain structural position guard
polaz Nov 1, 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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ including bounded quantities, model alternatives and supported exclusions,
then be refined without changing their identity.
The workspace can [check recorded requirements](docs/inventory.md#recorded-requirements)
against permitted current facts, preserving missing, stale and conflicting evidence.
It includes [physical planning](docs/inventory.md#physical-plans-and-cable-ends) with exact
rack elevations, cable ends, material requirements, reservations and reviewed
component replacement. Manual results remain separate from verified service health.

## License

Expand Down
28 changes: 28 additions & 0 deletions crates/omloom-contracts/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ fn main() -> Result<(), Box<dyn Error>> {
.boxed(".omloom.workspace.v1.Reply.result.detail")
.boxed(".omloom.workspace.v1.Reply.result.vm_placement_review")
.boxed(".omloom.workspace.v1.HostImport.report")
// Keep the request stack bound; allocate this volume only for an explicit coverage assertion.
.boxed(".omloom.workspace.v1.PhysicalDefinition.occupancy_coverage")
.boxed(".omloom.workspace.v1.PhysicalDefinition.rack_grid")
.boxed(".omloom.workspace.v1.PhysicalDefinition.clearance")
.boxed(".omloom.workspace.v1.PhysicalMaterial.physical")
.boxed(".omloom.workspace.v1.PhysicalReplacement.feedback")
.file_descriptor_set_path(&descriptor)
.compile_protos(&[workspace], &["../../proto"])?;
let set = FileDescriptorSet::decode(fs::read(descriptor)?.as_slice())?;
Expand Down Expand Up @@ -67,8 +73,30 @@ fn main() -> Result<(), Box<dyn Error>> {
16
} else if (message.name() == "EvaluateRequirements" && field.name() == "nodes")
|| (message.name() == "RequirementGroup" && field.name() == "members")
|| (message.name() == "PhysicalPlan" && field.name() == "steps")
|| (message.name() == "PhysicalView" && field.name() == "definitions")
|| (message.name() == "PhysicalCompletion"
&& matches!(field.name(), "completed_steps" | "not_performed_steps"))
|| (message.name() == "PhysicalPlan" && field.name() == "materials")
|| (message.name() == "PhysicalMaterial" && field.name() == "allocations")
|| (message.name() == "PhysicalReplacement" && field.name() == "endpoints")
|| (message.name() == "PhysicalEndpointMappings" && field.name() == "pairs")
|| (message.name() == "PhysicalCableEnd" && field.name() == "channels")
|| (message.name() == "PhysicalChannelSet" && field.name() == "channels")
|| (message.name() == "PhysicalDefinition" && field.name() == "channels")
{
64
} else if message.name() == "PhysicalMaterial" && field.name() == "specifications" {
12
} else if message.name() == "PhysicalView"
&& matches!(
field.name(),
"occupancy" | "plans" | "positions" | "replacement_quality"
)
{
25
} else if message.name() == "PhysicalView" && field.name() == "items" {
256
} else {
0
}
Expand Down
53 changes: 31 additions & 22 deletions crates/omloom-contracts/src/equipment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,29 +221,13 @@ fn identity(value: &Option<EquipmentReference>) -> (&str, &str, &str) {
None => ("", "", ""),
}
}
/// One bounded semantic encoding for retries, changed-only storage and CSV.
/// Candidate/exclusion order is irrelevant; source and interpretation basis are not.
pub fn encode(value: &EquipmentClaim) -> Result<Vec<u8>, Error> {
/// Normalize an owned bounded claim for changed-only storage and returned plans.
pub fn canonicalize(value: &mut EquipmentClaim) -> Result<(), Error> {
validate(value)?;
let canonical = match &value.assertion {
Some(equipment_claim::Assertion::Quantity(q)) => {
q.scale == 0
|| q.lower.is_some_and(|n| n % 10 != 0)
|| q.upper.is_some_and(|n| n % 10 != 0)
}
Some(equipment_claim::Assertion::Alternatives(v)) => {
v.candidates.is_sorted_by(|a, b| {
(&a.value, identity(&a.reference)) <= (&b.value, identity(&b.reference))
}) && v.excluded.is_sorted_by(|a, b| {
(&a.value, identity(&a.reference)) <= (&b.value, identity(&b.reference))
})
}
_ => true,
};
if canonical {
return Ok(value.encode_to_vec());
}
let mut value = value.clone();
canonicalize_validated(value);
Ok(())
}
fn canonicalize_validated(value: &mut EquipmentClaim) {
if let Some(equipment_claim::Assertion::Quantity(q)) = &mut value.assertion {
while q.scale > 0
&& q.lower.is_none_or(|n| n % 10 == 0)
Expand All @@ -266,6 +250,31 @@ pub fn encode(value: &EquipmentClaim) -> Result<Vec<u8>, Error> {
(&a.value, identity(&a.reference)).cmp(&(&b.value, identity(&b.reference)))
});
}
}
/// One bounded semantic encoding for retries, changed-only storage and CSV.
/// Candidate/exclusion order is irrelevant; source and interpretation basis are not.
pub fn encode(value: &EquipmentClaim) -> Result<Vec<u8>, Error> {
validate(value)?;
let canonical = match &value.assertion {
Some(equipment_claim::Assertion::Quantity(q)) => {
q.scale == 0
|| q.lower.is_some_and(|n| n % 10 != 0)
|| q.upper.is_some_and(|n| n % 10 != 0)
}
Some(equipment_claim::Assertion::Alternatives(v)) => {
v.candidates.is_sorted_by(|a, b| {
(&a.value, identity(&a.reference)) <= (&b.value, identity(&b.reference))
}) && v.excluded.is_sorted_by(|a, b| {
(&a.value, identity(&a.reference)) <= (&b.value, identity(&b.reference))
})
}
_ => true,
};
if canonical {
return Ok(value.encode_to_vec());
}
let mut value = value.clone();
canonicalize_validated(&mut value);
Ok(value.encode_to_vec())
}

Expand Down
24 changes: 24 additions & 0 deletions crates/omloom-contracts/src/equipment/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,28 @@
use super::*;

// Owned plans and the persisted encoding must share normalization, including retry order and fixed-point spelling.
#[test]
fn owned_claim_canonicalization_matches_encoding_and_is_idempotent() {
let mut value = possible();
let encoded = encode(&value).expect("valid claim");
canonicalize(&mut value).expect("valid owned claim");
assert_eq!(value.encode_to_vec(), encoded);
let previous = value.clone();
canonicalize(&mut value).expect("already canonical claim");
assert_eq!(value, previous);
let mut quantity = EquipmentClaim {
basis: "Recorded physical measurement".into(),
assertion: Some(equipment_claim::Assertion::Quantity(QuantityRange {
lower: Some(1200),
upper: None,
scale: 2,
unit: "mm".into(),
})),
};
let encoded = encode(&quantity).expect("valid measurement");
canonicalize(&mut quantity).expect("owned measurement");
assert_eq!(quantity.encode_to_vec(), encoded);
}
fn possible() -> EquipmentClaim {
EquipmentClaim {
basis: "Front label and visible bays".into(),
Expand Down
1 change: 1 addition & 0 deletions crates/omloom-contracts/src/ids.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ id!(SourceId);
id!(AuthorityId);
id!(RelationId);
id!(OperationId);
id!(PlanId);
id!(ScanId);
id!(TrustId);
id!(CredentialBindingId);
Expand Down
48 changes: 48 additions & 0 deletions crates/omloom-contracts/tests/workspace.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,54 @@
use omloom_contracts::{Error, Limits, MemoryBudget, decode_workspace, workspace as api};
use prost::Message;

// Physical wire containers enforce repeated bounds before prost allocates their vectors.
#[test]
fn physical_channels_and_endpoint_maps_are_bounded_before_decode() {
let budget = MemoryBudget::new(16 * 1_048_576);
let channels = api::Request {
working: None,
command: Some(api::request::Command::Physical(api::Physical {
action: Some(api::physical::Action::Define(api::PhysicalDefinitionEdit {
definition: Some(api::PhysicalDefinition {
channels: vec![1; 65],
..Default::default()
}),
})),
})),
}
.encode_to_vec();
assert!(matches!(
decode_workspace(&channels, Limits::default(), &budget),
Err(Error::Limit)
));
assert_eq!(budget.used(), 0);
let mappings = api::Request {
working: None,
command: Some(api::request::Command::Physical(api::Physical {
action: Some(api::physical::Action::Save(api::PhysicalSave {
plan: Some(api::PhysicalPlan {
steps: vec![api::PhysicalStep {
action: Some(api::physical_step::Action::Replacement(
api::PhysicalReplacement {
endpoints: vec![Default::default(); 65],
..Default::default()
},
)),
..Default::default()
}],
..Default::default()
}),
})),
})),
}
.encode_to_vec();
assert!(matches!(
decode_workspace(&mappings, Limits::default(), &budget),
Err(Error::Limit)
));
assert_eq!(budget.used(), 0);
}

// Omitted wire input cannot implicitly request an exclusive allocation.
#[test]
fn network_sharing_requires_an_explicit_mechanism() {
Expand Down
29 changes: 28 additions & 1 deletion crates/omloom-store/src/disk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,23 @@ use std::{
type Properties = BTreeMap<String, Value>;
type Result<T> = std::result::Result<T, Error>;
const ROOT: &str = "installation";
const SCHEMA_VERSION: u64 = 14;
const SCHEMA_VERSION: u64 = 22;
const PHYSICAL_TEXT: &[&str] = &["basis", "mounting", "item", "enclosure", "old_item"];
const PHYSICAL_NUMBERS: &[&str] = &[
"material_count",
"expected",
"x_start",
"x_length",
"y_start",
"y_length",
"z_start",
"z_length",
"max_mass_g",
"demand_mw",
"capacity_mw",
"cooling_mw",
"cooling_capacity_mw",
];
const MAX_CHILDREN: usize = 2048;
mod deltas;
mod inventory;
Expand Down Expand Up @@ -374,6 +390,12 @@ impl Store {
}
let mut schema = LabelSchema::new_node_id("OmRow");
schema.add_property(PropertyDef::new("key", PropertyType::String).not_null());
for name in PHYSICAL_TEXT {
schema.add_property(PropertyDef::new(*name, PropertyType::String));
}
for name in PHYSICAL_NUMBERS {
schema.add_property(PropertyDef::new(*name, PropertyType::Int));
}
for name in ["candidate", "cursor", "snapshot"] {
schema.add_property(PropertyDef::new(name, PropertyType::String));
}
Expand Down Expand Up @@ -546,6 +568,11 @@ impl Store {
.keys()
.filter(|key| !current.properties.contains_key(*key))
.any(|key| {
if PHYSICAL_TEXT.contains(&key.as_str())
|| PHYSICAL_NUMBERS.contains(&key.as_str())
{
return false;
}
if [
"issuer",
"subject",
Expand Down
Loading
Loading