diff --git a/dsl/extdeps/os/free.dag b/dsl/extdeps/os/free.dag new file mode 100644 index 00000000000..1750b48f296 --- /dev/null +++ b/dsl/extdeps/os/free.dag @@ -0,0 +1,32 @@ +module extdeps.os.free + +import std.measure { ByteSize } +import extdeps.external_authority { ExternalAuthority } +import extdeps.uri { Uri, Https } + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "man7.org/linux/man-pages/man1/free.1.html" + } +} + +type MemoryStats { + total: ByteSize + used: ByteSize + free: ByteSize + shared: ByteSize + buff_cache: ByteSize + available: ByteSize +} + +type SwapStats { + total: ByteSize + used: ByteSize + free: ByteSize +} + +type FreeOutput { + memory: MemoryStats + swap: SwapStats +} diff --git a/dsl/extdeps/os/proc_meminfo.dag b/dsl/extdeps/os/proc_meminfo.dag new file mode 100644 index 00000000000..2fcf280588e --- /dev/null +++ b/dsl/extdeps/os/proc_meminfo.dag @@ -0,0 +1,40 @@ +module extdeps.os.proc_meminfo + +import std.types { NonEmptyStr } +import std.list { List } +import std.measure { Kibibyte } +import extdeps.external_authority { ExternalAuthority } +import extdeps.uri { Uri, Https } + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "man7.org/linux/man-pages/man5/proc.5.html#/proc/meminfo" + } +} + +type MemoryMetric { + key: NonEmptyStr + value: Kibibyte +} + +type ProcMeminfo { + mem_total: Kibibyte + mem_free: Kibibyte + mem_available: Kibibyte + buffers: Kibibyte + cached: Kibibyte + swap_total: Kibibyte + swap_free: Kibibyte + all_metrics: List +} + +data meminfo_field_names: List = [ + "MemTotal", + "MemFree", + "MemAvailable", + "Buffers", + "Cached", + "SwapTotal", + "SwapFree", +] diff --git a/dsl/extdeps/os/systemd.dag b/dsl/extdeps/os/systemd.dag new file mode 100644 index 00000000000..40de5d849bb --- /dev/null +++ b/dsl/extdeps/os/systemd.dag @@ -0,0 +1,57 @@ +module extdeps.os.systemd + +import std.types { NonEmptyStr } +import std.list { List } +import std.serialization { CoproductWireContract, StringVariant, SnakeCase } +import extdeps.external_authority { ExternalAuthority } +import extdeps.uri { Uri, Https } + +data extdeps_external_authority_anchor: ExternalAuthority = ExternalAuthority { + uri: Uri { + scheme: Https + locator: "man7.org/linux/man-pages/man1/systemctl.1.html#is-active" + } +} + +type SystemdUnitActiveState + = Active + | Inactive + | Activating + | Deactivating + | Reloading + | Failed + +data systemd_unit_active_state_wire_contract: CoproductWireContract = { + coproduct: "SystemdUnitActiveState", + encoding: StringVariant { naming: SnakeCase } +} + +data systemd_parse_label_derivation_debt: List = [ + "dissolve-on: when contract-driven derivation is available (§3 single authority), derive parse/label from wire contract instead of hand-rolling", +] + +fn parse_systemd_unit_active_state(raw: String) -> SystemdUnitActiveState? { + if raw == "active" { Present { value: Active } } + else { if raw == "inactive" { Present { value: Inactive } } + else { if raw == "activating" { Present { value: Activating } } + else { if raw == "deactivating" { Present { value: Deactivating } } + else { if raw == "reloading" { Present { value: Reloading } } + else { if raw == "failed" { Present { value: Failed } } + else { none } } } } } } +} + +fn systemd_unit_active_state_wire_label(s: SystemdUnitActiveState) -> String { + match s { + Active => "active" + Inactive => "inactive" + Activating => "activating" + Deactivating => "deactivating" + Reloading => "reloading" + Failed => "failed" + } +} + +type SystemdUnitStatus { + name: NonEmptyStr + active_state: SystemdUnitActiveState +} diff --git a/dsl/std/measure.dag b/dsl/std/measure.dag index 6467fce181a..3041185c286 100644 --- a/dsl/std/measure.dag +++ b/dsl/std/measure.dag @@ -138,6 +138,8 @@ fn time_measure(count: Nat) -> Measure { type ByteSize = Measure +type Kibibyte = Measure + type Gibibyte = Measure fn gibibyte(count: Nat) -> Gibibyte { diff --git a/dsl/test/claim/os_free_witness_test.dag b/dsl/test/claim/os_free_witness_test.dag new file mode 100644 index 00000000000..4eade30f999 --- /dev/null +++ b/dsl/test/claim/os_free_witness_test.dag @@ -0,0 +1,38 @@ +module test.claim.os_free_witness + +import extdeps.os.free { + FreeOutput, + MemoryStats, + SwapStats, +} +import std.measure { ByteSize, byte_size, byte_size_count } + +test fn free_output_witnesses() -> Bool { + let memory_stats = MemoryStats { + total: byte_size(count: 8192000000), + used: byte_size(count: 1024000000), + free: byte_size(count: 2048000000), + shared: byte_size(count: 0), + buff_cache: byte_size(count: 5120000000), + available: byte_size(count: 3072000000), + } + + let swap_stats = SwapStats { + total: byte_size(count: 2048000000), + used: byte_size(count: 0), + free: byte_size(count: 2048000000), + } + + let output = FreeOutput { + memory: memory_stats, + swap: swap_stats, + } + + byte_size_count(b: memory_stats.total) > 0 + && byte_size_count(b: memory_stats.available) <= byte_size_count(b: memory_stats.total) + && byte_size_count(b: memory_stats.free) <= byte_size_count(b: memory_stats.total) + && byte_size_count(b: swap_stats.used) <= byte_size_count(b: swap_stats.total) + && byte_size_count(b: swap_stats.free) <= byte_size_count(b: swap_stats.total) + && byte_size_count(b: output.memory.total) > 0 + && byte_size_count(b: memory_stats.available) >= byte_size_count(b: memory_stats.free) +} diff --git a/dsl/test/claim/os_proc_meminfo_witness_test.dag b/dsl/test/claim/os_proc_meminfo_witness_test.dag new file mode 100644 index 00000000000..10aae5aff30 --- /dev/null +++ b/dsl/test/claim/os_proc_meminfo_witness_test.dag @@ -0,0 +1,31 @@ +module test.claim.os_proc_meminfo_witness + +import extdeps.os.proc_meminfo { + ProcMeminfo, + MemoryMetric, + meminfo_field_names, +} +import std.measure { Kibibyte, measure_count } +import std.types { NonEmptyStr } + +test fn proc_meminfo_witnesses() -> Bool { + let info = ProcMeminfo { + mem_total: Kibibyte { count: 8012272 }, + mem_free: Kibibyte { count: 4048136 }, + mem_available: Kibibyte { count: 6144000 }, + buffers: Kibibyte { count: 256000 }, + cached: Kibibyte { count: 2048136 }, + swap_total: Kibibyte { count: 2097152 }, + swap_free: Kibibyte { count: 2097152 }, + all_metrics: [], + } + + let metric = MemoryMetric { + key: "MemTotal" as NonEmptyStr, + value: Kibibyte { count: 8012272 }, + } + + measure_count(m: info.mem_total) > 0 + && measure_count(m: info.mem_available) <= measure_count(m: info.mem_total) + && measure_count(m: metric.value) > 0 +} diff --git a/dsl/test/claim/os_systemd_witness_test.dag b/dsl/test/claim/os_systemd_witness_test.dag new file mode 100644 index 00000000000..22c7911e1c4 --- /dev/null +++ b/dsl/test/claim/os_systemd_witness_test.dag @@ -0,0 +1,30 @@ +module test.claim.os_systemd_witness + +import extdeps.os.systemd { + SystemdUnitStatus, + SystemdUnitActiveState, + Active, + Inactive, + Failed, + parse_systemd_unit_active_state, + systemd_unit_active_state_wire_label, +} +import std.types { NonEmptyStr } + +test fn witness_active_wire_decodes_to_active() -> Bool { + match parse_systemd_unit_active_state(raw: "active") { + Present { value: Active } => true + _ => false + } +} + +test fn witness_wrong_case_does_not_decode() -> Bool { + match parse_systemd_unit_active_state(raw: "ACTIVE") { + none => true + _ => false + } +} + +test fn witness_roundtrip_failed() -> Bool { + systemd_unit_active_state_wire_label(s: Failed) == "failed" +} diff --git a/src/v1/stage0/src/inert_carrier_project.rs b/src/v1/stage0/src/inert_carrier_project.rs index aeb03925ed2..85240844d4c 100644 --- a/src/v1/stage0/src/inert_carrier_project.rs +++ b/src/v1/stage0/src/inert_carrier_project.rs @@ -61,9 +61,12 @@ const INERT_CARRIER_ROSTER: &[&str] = &[ "CargoPackage", // dsl/extdeps/rust/cargo.dag — Cargo manifest package row; unwired. "FilePermissions", // dsl/extdeps/access/posix.dag — POSIX mode permission carrier; no fs consumer. "FloorWitnessRow", // src/v2/workflow/affected_set_floor_runner.dag — runner row; self-only. + "FreeOutput", // dsl/extdeps/os/free.dag — host metrics shape; shadow lane, no workflow consumer yet. "GitCliReportedVersion", // dsl/extdeps/git/versioning.dag — git --version parse target; unwired. + "ProcMeminfo", // dsl/extdeps/os/proc_meminfo.dag — /proc/meminfo parsed shape; shadow lane, no workflow consumer yet. "ReactHookSite", // src/v2/extdeps/frameworks/react.dag — React hook-site model; unwired. - "SecretValue", // dsl/std/types.dag — secret-string carrier; no redaction consumer wired. + "SecretValue", // dsl/std/types.dag — secret-string carrier; no redaction consumer wired. + "SystemdUnitStatus", // dsl/extdeps/os/systemd.dag — systemd unit status shape; shadow lane, no workflow consumer yet. ]; /// Extract top-level `type NAME` carrier declarations with their full declaration BLOCK text.