From 8b1ae110c2a5940e0bf11f1e76a9160dd4b1debd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:19:19 +0900 Subject: [PATCH 01/25] test(resource): define bounded browser process-set RSS contract --- .../tests/browser_process_set_rss.rs | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 crates/originweave-resource/tests/browser_process_set_rss.rs diff --git a/crates/originweave-resource/tests/browser_process_set_rss.rs b/crates/originweave-resource/tests/browser_process_set_rss.rs new file mode 100644 index 000000000..988c49212 --- /dev/null +++ b/crates/originweave-resource/tests/browser_process_set_rss.rs @@ -0,0 +1,86 @@ +#![allow(clippy::expect_used)] + +use originweave_resource::{ + BrowserRssSampleError, MAX_BROWSER_PROCESS_SET_SIZE, + aggregate_browser_process_rss_samples, sample_linux_process_rss_bytes, + sample_linux_process_set_rss_bytes, +}; + +#[test] +fn process_set_aggregation_counts_each_process_once() { + assert_eq!( + aggregate_browser_process_rss_samples(&[(11, 1_024), (12, 2_048), (13, 4_096)]), + Ok(7_168) + ); +} + +#[test] +fn process_set_aggregation_rejects_ambiguous_or_unbounded_membership() { + assert_eq!( + aggregate_browser_process_rss_samples(&[]), + Err(BrowserRssSampleError::EmptyProcessSet) + ); + assert_eq!( + aggregate_browser_process_rss_samples(&[(0, 1)]), + Err(BrowserRssSampleError::InvalidProcessId) + ); + assert_eq!( + aggregate_browser_process_rss_samples(&[(42, 1), (42, 2)]), + Err(BrowserRssSampleError::DuplicateProcessId) + ); + + let oversized: Vec<(u32, u64)> = (1..=(MAX_BROWSER_PROCESS_SET_SIZE as u32 + 1)) + .map(|process_id| (process_id, 1)) + .collect(); + assert_eq!( + aggregate_browser_process_rss_samples(&oversized), + Err(BrowserRssSampleError::ProcessSetTooLarge) + ); +} + +#[test] +fn process_set_aggregation_rejects_overflow_instead_of_undercounting() { + assert_eq!( + aggregate_browser_process_rss_samples(&[(1, u64::MAX), (2, 1)]), + Err(BrowserRssSampleError::ProcessSetRssOverflow) + ); +} + +#[test] +fn linux_process_set_sampler_matches_the_single_process_sampler() { + #[cfg(target_os = "linux")] + { + let process_id = std::process::id(); + let single = sample_linux_process_rss_bytes(process_id) + .expect("the current Linux test process has a readable /proc status"); + assert_eq!(sample_linux_process_set_rss_bytes(&[process_id]), Ok(single)); + } + + #[cfg(not(target_os = "linux"))] + assert_eq!( + sample_linux_process_set_rss_bytes(&[std::process::id()]), + Err(BrowserRssSampleError::UnsupportedPlatform) + ); +} + +#[test] +fn linux_process_set_sampler_fails_closed_for_invalid_or_partial_sets() { + assert_eq!( + sample_linux_process_set_rss_bytes(&[]), + Err(BrowserRssSampleError::EmptyProcessSet) + ); + assert_eq!( + sample_linux_process_set_rss_bytes(&[1, 1]), + Err(BrowserRssSampleError::DuplicateProcessId) + ); + assert_eq!( + sample_linux_process_set_rss_bytes(&[0]), + Err(BrowserRssSampleError::InvalidProcessId) + ); + + #[cfg(target_os = "linux")] + assert_eq!( + sample_linux_process_set_rss_bytes(&[std::process::id(), u32::MAX]), + Err(BrowserRssSampleError::ProcessStatusUnavailable) + ); +} From 65e0cb7355b21a944f42a1976cfe0fa482ded89b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:22:01 +0900 Subject: [PATCH 02/25] test(resource): apply canonical process-set test formatting --- .../tests/browser_process_set_rss.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/originweave-resource/tests/browser_process_set_rss.rs b/crates/originweave-resource/tests/browser_process_set_rss.rs index 988c49212..96937e95e 100644 --- a/crates/originweave-resource/tests/browser_process_set_rss.rs +++ b/crates/originweave-resource/tests/browser_process_set_rss.rs @@ -1,9 +1,8 @@ #![allow(clippy::expect_used)] use originweave_resource::{ - BrowserRssSampleError, MAX_BROWSER_PROCESS_SET_SIZE, - aggregate_browser_process_rss_samples, sample_linux_process_rss_bytes, - sample_linux_process_set_rss_bytes, + BrowserRssSampleError, MAX_BROWSER_PROCESS_SET_SIZE, aggregate_browser_process_rss_samples, + sample_linux_process_rss_bytes, sample_linux_process_set_rss_bytes, }; #[test] @@ -53,7 +52,10 @@ fn linux_process_set_sampler_matches_the_single_process_sampler() { let process_id = std::process::id(); let single = sample_linux_process_rss_bytes(process_id) .expect("the current Linux test process has a readable /proc status"); - assert_eq!(sample_linux_process_set_rss_bytes(&[process_id]), Ok(single)); + assert_eq!( + sample_linux_process_set_rss_bytes(&[process_id]), + Ok(single) + ); } #[cfg(not(target_os = "linux"))] From 25d288d1b235a00f83c2326e0948db8b080651c0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:25:47 +0900 Subject: [PATCH 03/25] feat(resource): aggregate bounded browser process-set RSS --- crates/originweave-resource/src/lib.rs | 86 ++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index 5746e6fc6..69eb0d578 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -11,6 +11,9 @@ const MEBIBYTE_BYTES: u64 = 1_048_576; +/// Maximum number of explicitly attributed browser processes in one RSS sample set. +pub const MAX_BROWSER_PROCESS_SET_SIZE: usize = 256; + const fn bytes_to_mebibytes_ceil(bytes: u64) -> u64 { let whole_mebibytes = bytes / MEBIBYTE_BYTES; if bytes.is_multiple_of(MEBIBYTE_BYTES) { @@ -402,6 +405,14 @@ pub enum BrowserTaskTelemetryError { pub enum BrowserRssSampleError { /// Process identifiers are one-based and zero was supplied. InvalidProcessId, + /// No browser process was supplied to a process-set measurement. + EmptyProcessSet, + /// A process identifier appeared more than once and would be double-counted. + DuplicateProcessId, + /// The caller supplied more process identifiers than the bounded set permits. + ProcessSetTooLarge, + /// Summing bounded process resident-set sizes would overflow `u64`. + ProcessSetRssOverflow, /// The process status file could not be read at the sampling boundary. ProcessStatusUnavailable, /// The Linux process status did not contain a resident-set-size field. @@ -418,6 +429,51 @@ pub enum BrowserRssSampleError { UnsupportedPlatform, } +fn validate_browser_process_set_size(process_count: usize) -> Result<(), BrowserRssSampleError> { + if process_count == 0 { + return Err(BrowserRssSampleError::EmptyProcessSet); + } + if process_count > MAX_BROWSER_PROCESS_SET_SIZE { + return Err(BrowserRssSampleError::ProcessSetTooLarge); + } + Ok(()) +} + +fn validate_browser_process_ids(process_ids: &[u32]) -> Result<(), BrowserRssSampleError> { + for (index, process_id) in process_ids.iter().copied().enumerate() { + if process_id == 0 { + return Err(BrowserRssSampleError::InvalidProcessId); + } + if process_ids[..index].contains(&process_id) { + return Err(BrowserRssSampleError::DuplicateProcessId); + } + } + Ok(()) +} + +/// Aggregate exact caller-supplied process RSS samples without double-counting. +/// +/// The input is deliberately an explicit bounded process set rather than a +/// discovered browser tree. Process identifiers must be nonzero and unique, +/// and byte totals use checked addition so an overflow cannot be misreported as +/// a smaller task. This function does not prove that any process belongs to +/// Chromium or to the same Agent Task. +pub fn aggregate_browser_process_rss_samples( + samples: &[(u32, u64)], +) -> Result { + validate_browser_process_set_size(samples.len())?; + let process_ids: Vec = samples.iter().map(|(process_id, _rss_bytes)| *process_id).collect(); + validate_browser_process_ids(&process_ids)?; + + let mut total_rss_bytes = 0_u64; + for (_process_id, rss_bytes) in samples { + total_rss_bytes = total_rss_bytes + .checked_add(*rss_bytes) + .ok_or(BrowserRssSampleError::ProcessSetRssOverflow)?; + } + Ok(total_rss_bytes) +} + /// Parse Linux `/proc//status` and return the exact `VmRSS` value in bytes. /// /// Linux reports `VmRSS` in `kB`, where the kernel ABI uses 1024-byte units. @@ -484,3 +540,33 @@ pub fn sample_linux_process_rss_bytes(process_id: u32) -> Result Result { + validate_browser_process_set_size(process_ids.len())?; + validate_browser_process_ids(process_ids)?; + + #[cfg(target_os = "linux")] + { + let mut samples = Vec::with_capacity(process_ids.len()); + for process_id in process_ids { + let rss_bytes = sample_linux_process_rss_bytes(*process_id)?; + samples.push((*process_id, rss_bytes)); + } + aggregate_browser_process_rss_samples(&samples) + } + + #[cfg(not(target_os = "linux"))] + { + let _ = process_ids; + Err(BrowserRssSampleError::UnsupportedPlatform) + } +} From 61ddf7bb085cc91c08ca393efb3da705b45dd6f4 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:28:26 +0900 Subject: [PATCH 04/25] style(resource): apply canonical process-set formatting --- crates/originweave-resource/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index 69eb0d578..d6766815b 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -462,7 +462,10 @@ pub fn aggregate_browser_process_rss_samples( samples: &[(u32, u64)], ) -> Result { validate_browser_process_set_size(samples.len())?; - let process_ids: Vec = samples.iter().map(|(process_id, _rss_bytes)| *process_id).collect(); + let process_ids: Vec = samples + .iter() + .map(|(process_id, _rss_bytes)| *process_id) + .collect(); validate_browser_process_ids(&process_ids)?; let mut total_rss_bytes = 0_u64; From 8958b55d0ef1b135be3d2a48dc61c604447cb01a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:34:00 +0900 Subject: [PATCH 05/25] test(resource): avoid racy sequential RSS comparison --- .../tests/browser_process_set_rss.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/originweave-resource/tests/browser_process_set_rss.rs b/crates/originweave-resource/tests/browser_process_set_rss.rs index 96937e95e..e073ee344 100644 --- a/crates/originweave-resource/tests/browser_process_set_rss.rs +++ b/crates/originweave-resource/tests/browser_process_set_rss.rs @@ -1,8 +1,6 @@ -#![allow(clippy::expect_used)] - use originweave_resource::{ BrowserRssSampleError, MAX_BROWSER_PROCESS_SET_SIZE, aggregate_browser_process_rss_samples, - sample_linux_process_rss_bytes, sample_linux_process_set_rss_bytes, + sample_linux_process_set_rss_bytes, }; #[test] @@ -46,15 +44,13 @@ fn process_set_aggregation_rejects_overflow_instead_of_undercounting() { } #[test] -fn linux_process_set_sampler_matches_the_single_process_sampler() { +fn linux_process_set_sampler_measures_the_current_process() { #[cfg(target_os = "linux")] { - let process_id = std::process::id(); - let single = sample_linux_process_rss_bytes(process_id) - .expect("the current Linux test process has a readable /proc status"); - assert_eq!( - sample_linux_process_set_rss_bytes(&[process_id]), - Ok(single) + let sampled = sample_linux_process_set_rss_bytes(&[std::process::id()]); + assert!( + matches!(&sampled, Ok(rss_bytes) if *rss_bytes > 0 && *rss_bytes % 1_024 == 0), + "the current Linux test process must produce a positive kernel-kB RSS sample: {sampled:?}" ); } From 986958ab8a29b3ca708c80e44df45e1ec5f9f868 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 09:40:16 +0900 Subject: [PATCH 06/25] docs(resource): record bounded process-set RSS sampling --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebb53ff0b..de49a9cf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Real loopback TCP integration proof plus deterministic timeout, refusal, retry, peer-inspection, peer-mismatch, canonicalization, IPv6 metadata, and single-use replay tests. - Real loopback rustls integration covering trusted DNS SAN, Common-Name fallback rejection, wrong-name and untrusted-root rejection, fixed-time expiry and not-yet-valid failures, exact IPv4 and IPv6 SANs, TLS 1.2/TLS 1.3, required and optional ALPN, and transport-origin binding. - Cumulative interactive-first RAM, VRAM, batch, local-model, admission, pause, and compositor-pressure mitigation plans, including active-consumer reduction at exact hard limits. -- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; can conservatively feed RSS into the resource governor; and can sample one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling. The sampler performs no Chromium process discovery, child-process aggregation, cgroup accounting, GPU/heap measurement, or cross-platform sampling. +- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; conservatively feeds supplied RSS into the resource governor; samples one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling; and aggregates or samples an explicit process set of at most 256 unique nonzero PIDs with checked RSS addition and fail-closed all-member sampling. These adapters perform no Chromium process discovery, child-process/cgroup attribution, GPU/heap measurement, or cross-platform sampling. - Universally value-redacted network evidence with explicit path, metadata, and provenance bounds; ambiguous path rejection; validated source URLs; lowercase SHA-256 identifiers; and verification state. - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. @@ -74,4 +74,4 @@ All notable changes to OriginWeave are documented in this file. The format follo - The hourly product agent has no Git metadata or repository authority. A separate post-verification publisher opens one PR and cannot approve or merge it. - The unprivileged OpenCode user is restricted to loopback egress during model execution, preventing runner-wide allow-listed endpoints from becoming direct source-exfiltration channels. -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file From a82626dba4c01e4778555b71bc15366b2d2cfb6e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 08:35:22 +0900 Subject: [PATCH 07/25] fix(resource): preserve strict VmRSS syntax after stack reconcile --- crates/originweave-resource/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index d6766815b..adcd1c1d3 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -507,6 +507,9 @@ pub fn parse_linux_proc_status_rss_bytes(status: &str) -> Result() .map_err(|_error| BrowserRssSampleError::InvalidVmRss)?; From 29a410cf4f2396a5ec25d4774cc381c163a87e0b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 08:36:21 +0900 Subject: [PATCH 08/25] docs(resource): record bounded process-set RSS on live stack --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f636017f7..b9e0db053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Real loopback TCP integration proof plus deterministic timeout, refusal, retry, peer-inspection, peer-mismatch, canonicalization, IPv6 metadata, and single-use replay tests. - Real loopback rustls integration covering trusted DNS SAN, Common-Name fallback rejection, wrong-name and untrusted-root rejection, fixed-time expiry and not-yet-valid failures, exact IPv4 and IPv6 SANs, TLS 1.2/TLS 1.3, required and optional ALPN, and transport-origin binding. - Cumulative interactive-first RAM, VRAM, batch, local-model, admission, pause, and compositor-pressure mitigation plans, including active-consumer reduction at exact hard limits. -- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; can conservatively feed RSS into the resource governor; and can sample one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling (Linux Kernel Documentation, n.d.). The sampler performs no Chromium process discovery, child-process aggregation, cgroup accounting, GPU/heap measurement, or cross-platform sampling. +- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; conservatively feeds supplied RSS into the resource governor; samples one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling; and aggregates or samples an explicit process set of at most 256 unique nonzero PIDs with checked RSS addition and fail-closed all-member sampling (Linux Kernel Documentation, n.d.). These adapters perform no Chromium process discovery, child-process/cgroup attribution, GPU/heap measurement, or cross-platform sampling. - Universally value-redacted network evidence with explicit path, metadata, and provenance bounds; ambiguous path rejection; validated source URLs; lowercase SHA-256 identifiers; and verification state. - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. From cd8f41894e139bcb49756c15a026330b67756af0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 14:51:51 +0900 Subject: [PATCH 09/25] test(resource): bind RSS sampling to Linux process identity --- .../tests/browser_process_identity_rss.rs | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 crates/originweave-resource/tests/browser_process_identity_rss.rs diff --git a/crates/originweave-resource/tests/browser_process_identity_rss.rs b/crates/originweave-resource/tests/browser_process_identity_rss.rs new file mode 100644 index 000000000..557da062a --- /dev/null +++ b/crates/originweave-resource/tests/browser_process_identity_rss.rs @@ -0,0 +1,101 @@ +use originweave_resource::{ + BrowserRssSampleError, LinuxProcessIdentity, parse_linux_proc_stat_start_time_ticks, + read_linux_process_identity, sample_linux_process_identity_rss_bytes, + sample_linux_process_identity_set_rss_bytes, verify_linux_process_identity, +}; + +fn proc_stat_with_start_time(start_time_ticks: u64) -> String { + format!( + "42 (chrome worker)) S 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 {start_time_ticks} 999" + ) +} + +#[test] +fn proc_stat_parser_extracts_start_time_after_complex_comm() { + assert_eq!( + parse_linux_proc_stat_start_time_ticks(&proc_stat_with_start_time(987_654)), + Ok(987_654) + ); +} + +#[test] +fn proc_stat_parser_rejects_missing_or_malformed_start_time() { + assert_eq!( + parse_linux_proc_stat_start_time_ticks("42 chrome S 1 2 3"), + Err(BrowserRssSampleError::InvalidProcessStat) + ); + assert_eq!( + parse_linux_proc_stat_start_time_ticks(&proc_stat_with_start_time(0).replace(" 0 999", " nope 999")), + Err(BrowserRssSampleError::InvalidProcessStat) + ); +} + +#[test] +fn process_identity_binds_pid_to_kernel_start_time() -> Result<(), BrowserRssSampleError> { + let identity = LinuxProcessIdentity::new(42, 987_654)?; + assert_eq!(identity.process_id(), 42); + assert_eq!(identity.start_time_ticks(), 987_654); + assert_eq!( + verify_linux_process_identity(identity, &proc_stat_with_start_time(987_654)), + Ok(()) + ); + assert_eq!( + verify_linux_process_identity(identity, &proc_stat_with_start_time(987_655)), + Err(BrowserRssSampleError::ProcessIdentityChanged) + ); + Ok(()) +} + +#[test] +fn process_identity_rejects_zero_pid() { + assert_eq!( + LinuxProcessIdentity::new(0, 1), + Err(BrowserRssSampleError::InvalidProcessId) + ); +} + +#[test] +fn linux_identity_sampler_rejects_pid_reuse_and_samples_current_process() -> Result<(), String> { + #[cfg(target_os = "linux")] + { + let identity = read_linux_process_identity(std::process::id()) + .map_err(|error| format!("read current process identity: {error:?}"))?; + let rss_bytes = sample_linux_process_identity_rss_bytes(identity) + .map_err(|error| format!("sample current process identity: {error:?}"))?; + assert!(rss_bytes > 0); + assert_eq!(rss_bytes % 1_024, 0); + + let stale = LinuxProcessIdentity::new( + identity.process_id(), + identity.start_time_ticks().wrapping_add(1), + ) + .map_err(|error| format!("construct stale identity: {error:?}"))?; + assert_eq!( + sample_linux_process_identity_rss_bytes(stale), + Err(BrowserRssSampleError::ProcessIdentityChanged) + ); + assert_eq!( + sample_linux_process_identity_set_rss_bytes(&[stale]), + Err(BrowserRssSampleError::ProcessIdentityChanged) + ); + } + + #[cfg(not(target_os = "linux"))] + { + let identity = LinuxProcessIdentity::new(std::process::id(), 1) + .map_err(|error| format!("construct process identity: {error:?}"))?; + assert_eq!( + read_linux_process_identity(std::process::id()), + Err(BrowserRssSampleError::UnsupportedPlatform) + ); + assert_eq!( + sample_linux_process_identity_rss_bytes(identity), + Err(BrowserRssSampleError::UnsupportedPlatform) + ); + assert_eq!( + sample_linux_process_identity_set_rss_bytes(&[identity]), + Err(BrowserRssSampleError::UnsupportedPlatform) + ); + } + Ok(()) +} From 7b39fa5f51636488170c61b73554529e31bbc660 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 14:54:19 +0900 Subject: [PATCH 10/25] style(resource): apply canonical identity test formatting --- .../tests/browser_process_identity_rss.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/originweave-resource/tests/browser_process_identity_rss.rs b/crates/originweave-resource/tests/browser_process_identity_rss.rs index 557da062a..bb01d806f 100644 --- a/crates/originweave-resource/tests/browser_process_identity_rss.rs +++ b/crates/originweave-resource/tests/browser_process_identity_rss.rs @@ -25,7 +25,9 @@ fn proc_stat_parser_rejects_missing_or_malformed_start_time() { Err(BrowserRssSampleError::InvalidProcessStat) ); assert_eq!( - parse_linux_proc_stat_start_time_ticks(&proc_stat_with_start_time(0).replace(" 0 999", " nope 999")), + parse_linux_proc_stat_start_time_ticks( + &proc_stat_with_start_time(0).replace(" 0 999", " nope 999") + ), Err(BrowserRssSampleError::InvalidProcessStat) ); } From 922861b9acdbe67eeee27c4b788bfd1d32729142 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 17:44:57 +0900 Subject: [PATCH 11/25] feat(resource): bind RSS sampling to Linux process identity --- crates/originweave-resource/src/lib.rs | 179 +++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index adcd1c1d3..4724f1674 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -413,6 +413,12 @@ pub enum BrowserRssSampleError { ProcessSetTooLarge, /// Summing bounded process resident-set sizes would overflow `u64`. ProcessSetRssOverflow, + /// The Linux process stat file could not be read at the identity boundary. + ProcessStatUnavailable, + /// The Linux process stat record was malformed or lacked its start-time field. + InvalidProcessStat, + /// The PID no longer refers to the kernel process instance bound by the identity. + ProcessIdentityChanged, /// The process status file could not be read at the sampling boundary. ProcessStatusUnavailable, /// The Linux process status did not contain a resident-set-size field. @@ -451,6 +457,142 @@ fn validate_browser_process_ids(process_ids: &[u32]) -> Result<(), BrowserRssSam Ok(()) } +/// A Linux PID bound to the kernel start-time tick recorded in `/proc//stat`. +/// +/// A PID by itself is reusable and therefore insufficient as long-lived browser +/// process authority. This value pairs the caller-attributed PID with Linux +/// field 22 (`starttime`) so RSS sampling can reject a PID that has been reused +/// for a different process instance. It does not prove Chromium/task ownership. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct LinuxProcessIdentity { + process_id: u32, + start_time_ticks: u64, +} + +impl LinuxProcessIdentity { + /// Construct one process identity from a nonzero PID and kernel start-time tick. + pub const fn new( + process_id: u32, + start_time_ticks: u64, + ) -> Result { + if process_id == 0 { + return Err(BrowserRssSampleError::InvalidProcessId); + } + Ok(Self { + process_id, + start_time_ticks, + }) + } + + /// Return the Linux process identifier bound by this identity. + #[must_use] + pub const fn process_id(self) -> u32 { + self.process_id + } + + /// Return Linux `/proc//stat` field 22 in clock ticks since boot. + #[must_use] + pub const fn start_time_ticks(self) -> u64 { + self.start_time_ticks + } +} + +fn parse_linux_proc_stat_identity(stat: &str) -> Result<(u32, u64), BrowserRssSampleError> { + let Some(open_comm_index) = stat.find(" (") else { + return Err(BrowserRssSampleError::InvalidProcessStat); + }; + let Some(close_comm_index) = stat.rfind(") ") else { + return Err(BrowserRssSampleError::InvalidProcessStat); + }; + if close_comm_index <= open_comm_index + 1 { + return Err(BrowserRssSampleError::InvalidProcessStat); + } + + let process_id_text = &stat[..open_comm_index]; + if process_id_text.is_empty() + || !process_id_text.bytes().all(|byte| byte.is_ascii_digit()) + { + return Err(BrowserRssSampleError::InvalidProcessStat); + } + let process_id = process_id_text + .parse::() + .map_err(|_error| BrowserRssSampleError::InvalidProcessStat)?; + if process_id == 0 { + return Err(BrowserRssSampleError::InvalidProcessStat); + } + + let mut fields_after_comm = stat[close_comm_index + 2..].split_whitespace(); + let Some(_state) = fields_after_comm.next() else { + return Err(BrowserRssSampleError::InvalidProcessStat); + }; + let Some(start_time_text) = fields_after_comm.nth(18) else { + return Err(BrowserRssSampleError::InvalidProcessStat); + }; + if !start_time_text.bytes().all(|byte| byte.is_ascii_digit()) { + return Err(BrowserRssSampleError::InvalidProcessStat); + } + let start_time_ticks = start_time_text + .parse::() + .map_err(|_error| BrowserRssSampleError::InvalidProcessStat)?; + Ok((process_id, start_time_ticks)) +} + +/// Parse Linux `/proc//stat` field 22 (`starttime`) in kernel clock ticks. +/// +/// The command name is parenthesized and may itself contain spaces or closing +/// parentheses, so this parser anchors on the final `") "` delimiter instead +/// of splitting the complete record on whitespace. Malformed or truncated +/// records fail closed rather than producing a reusable PID-only identity. +pub fn parse_linux_proc_stat_start_time_ticks( + stat: &str, +) -> Result { + parse_linux_proc_stat_identity(stat).map(|(_process_id, start_time_ticks)| start_time_ticks) +} + +/// Verify that one Linux stat record still represents the supplied process identity. +/// +/// Both PID and kernel start time must match. A syntactically valid record for a +/// different process instance returns [`BrowserRssSampleError::ProcessIdentityChanged`]. +pub fn verify_linux_process_identity( + identity: LinuxProcessIdentity, + stat: &str, +) -> Result<(), BrowserRssSampleError> { + let (process_id, start_time_ticks) = parse_linux_proc_stat_identity(stat)?; + if process_id != identity.process_id || start_time_ticks != identity.start_time_ticks { + return Err(BrowserRssSampleError::ProcessIdentityChanged); + } + Ok(()) +} + +/// Read the current Linux kernel identity for one explicit process identifier. +/// +/// This function reads only `/proc//stat`; it does not discover processes +/// or establish Chromium/task ownership. Non-Linux platforms fail closed. +pub fn read_linux_process_identity( + process_id: u32, +) -> Result { + if process_id == 0 { + return Err(BrowserRssSampleError::InvalidProcessId); + } + + #[cfg(target_os = "linux")] + { + let stat = std::fs::read_to_string(format!("/proc/{process_id}/stat")) + .map_err(|_error| BrowserRssSampleError::ProcessStatUnavailable)?; + let (observed_process_id, start_time_ticks) = parse_linux_proc_stat_identity(&stat)?; + if observed_process_id != process_id { + return Err(BrowserRssSampleError::ProcessIdentityChanged); + } + LinuxProcessIdentity::new(observed_process_id, start_time_ticks) + } + + #[cfg(not(target_os = "linux"))] + { + let _ = process_id; + Err(BrowserRssSampleError::UnsupportedPlatform) + } +} + /// Aggregate exact caller-supplied process RSS samples without double-counting. /// /// The input is deliberately an explicit bounded process set rather than a @@ -547,6 +689,43 @@ pub fn sample_linux_process_rss_bytes(process_id: u32) -> Result/stat` is read and compared with the +/// supplied kernel start-time identity. If the PID was reused at or before the +/// verification point, the measurement is discarded. The function never turns +/// this operating-system identity into Chromium/task ownership authority. +pub fn sample_linux_process_identity_rss_bytes( + identity: LinuxProcessIdentity, +) -> Result { + let rss_bytes = sample_linux_process_rss_bytes(identity.process_id)?; + let current_identity = read_linux_process_identity(identity.process_id)?; + if current_identity != identity { + return Err(BrowserRssSampleError::ProcessIdentityChanged); + } + Ok(rss_bytes) +} + +/// Sample the aggregate RSS of one explicit bounded Linux process-identity set. +/// +/// Every PID must be nonzero and unique. Each member is sampled through the +/// PID-plus-start-time identity check before aggregation, so one stale/reused +/// PID fails the complete measurement rather than contributing ambiguous RSS. +pub fn sample_linux_process_identity_set_rss_bytes( + identities: &[LinuxProcessIdentity], +) -> Result { + validate_browser_process_set_size(identities.len())?; + let process_ids: Vec = identities.iter().map(|identity| identity.process_id).collect(); + validate_browser_process_ids(&process_ids)?; + + let mut samples = Vec::with_capacity(identities.len()); + for identity in identities { + let rss_bytes = sample_linux_process_identity_rss_bytes(*identity)?; + samples.push((identity.process_id, rss_bytes)); + } + aggregate_browser_process_rss_samples(&samples) +} + /// Sample the aggregate RSS of one explicit bounded Linux process set. /// /// The caller owns process discovery and attribution. This function validates From bfff435599be2930904249f4eab61edd0a15d887 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 16 Aug 2026 17:51:14 +0900 Subject: [PATCH 12/25] test(resource): exhaust hostile Linux process identity cases --- .../tests/browser_process_identity_rss.rs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/crates/originweave-resource/tests/browser_process_identity_rss.rs b/crates/originweave-resource/tests/browser_process_identity_rss.rs index bb01d806f..bbe31229c 100644 --- a/crates/originweave-resource/tests/browser_process_identity_rss.rs +++ b/crates/originweave-resource/tests/browser_process_identity_rss.rs @@ -10,6 +10,10 @@ fn proc_stat_with_start_time(start_time_ticks: u64) -> String { ) } +fn proc_stat_for_pid(process_id: u32, start_time_ticks: u64) -> String { + proc_stat_with_start_time(start_time_ticks).replacen("42 ", &format!("{process_id} "), 1) +} + #[test] fn proc_stat_parser_extracts_start_time_after_complex_comm() { assert_eq!( @@ -32,6 +36,29 @@ fn proc_stat_parser_rejects_missing_or_malformed_start_time() { ); } +#[test] +fn proc_stat_parser_rejects_hostile_structure_and_numeric_fields() { + let invalid_cases = [ + "42 (unterminated S 1 2 3".to_owned(), + "42) (misordered S 1 2 3".to_owned(), + " (chrome) S 1 2 3".to_owned(), + "x (chrome) S 1 2 3".to_owned(), + proc_stat_with_start_time(1).replacen("42 ", "4294967296 ", 1), + proc_stat_with_start_time(1).replacen("42 ", "0 ", 1), + "42 (chrome) ".to_owned(), + "42 (chrome) S 1 2 3".to_owned(), + proc_stat_with_start_time(1).replace(" 1 999", " 18446744073709551616 999"), + ]; + + for stat in invalid_cases { + assert_eq!( + parse_linux_proc_stat_start_time_ticks(&stat), + Err(BrowserRssSampleError::InvalidProcessStat), + "unexpectedly accepted stat record: {stat:?}" + ); + } +} + #[test] fn process_identity_binds_pid_to_kernel_start_time() -> Result<(), BrowserRssSampleError> { let identity = LinuxProcessIdentity::new(42, 987_654)?; @@ -45,6 +72,14 @@ fn process_identity_binds_pid_to_kernel_start_time() -> Result<(), BrowserRssSam verify_linux_process_identity(identity, &proc_stat_with_start_time(987_655)), Err(BrowserRssSampleError::ProcessIdentityChanged) ); + assert_eq!( + verify_linux_process_identity(identity, &proc_stat_for_pid(43, 987_654)), + Err(BrowserRssSampleError::ProcessIdentityChanged) + ); + assert_eq!( + verify_linux_process_identity(identity, "malformed"), + Err(BrowserRssSampleError::InvalidProcessStat) + ); Ok(()) } @@ -54,6 +89,10 @@ fn process_identity_rejects_zero_pid() { LinuxProcessIdentity::new(0, 1), Err(BrowserRssSampleError::InvalidProcessId) ); + assert_eq!( + read_linux_process_identity(0), + Err(BrowserRssSampleError::InvalidProcessId) + ); } #[test] @@ -66,6 +105,11 @@ fn linux_identity_sampler_rejects_pid_reuse_and_samples_current_process() -> Res .map_err(|error| format!("sample current process identity: {error:?}"))?; assert!(rss_bytes > 0); assert_eq!(rss_bytes % 1_024, 0); + assert_eq!( + sample_linux_process_identity_set_rss_bytes(&[identity]) + .map_err(|error| format!("sample current identity set: {error:?}"))?, + rss_bytes + ); let stale = LinuxProcessIdentity::new( identity.process_id(), @@ -80,6 +124,25 @@ fn linux_identity_sampler_rejects_pid_reuse_and_samples_current_process() -> Res sample_linux_process_identity_set_rss_bytes(&[stale]), Err(BrowserRssSampleError::ProcessIdentityChanged) ); + assert_eq!( + sample_linux_process_identity_set_rss_bytes(&[]), + Err(BrowserRssSampleError::EmptyProcessSet) + ); + assert_eq!( + sample_linux_process_identity_set_rss_bytes(&[identity, identity]), + Err(BrowserRssSampleError::DuplicateProcessId) + ); + + let absent = LinuxProcessIdentity::new(u32::MAX, 1) + .map_err(|error| format!("construct absent process identity: {error:?}"))?; + assert_eq!( + read_linux_process_identity(u32::MAX), + Err(BrowserRssSampleError::ProcessStatUnavailable) + ); + assert_eq!( + sample_linux_process_identity_rss_bytes(absent), + Err(BrowserRssSampleError::ProcessStatusUnavailable) + ); } #[cfg(not(target_os = "linux"))] From dd0d1b3752a561e27cbd045d8fec217f48507cc8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 12:18:26 +0900 Subject: [PATCH 13/25] docs: preserve process-set RSS changelog after stack alignment --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 248632abc..ff1bccd65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Real loopback TCP integration proof plus deterministic timeout, refusal, retry, peer-inspection, peer-mismatch, canonicalization, IPv6 metadata, and single-use replay tests. - Real loopback rustls integration covering trusted DNS SAN, Common-Name fallback rejection, wrong-name and untrusted-root rejection, fixed-time expiry and not-yet-valid failures, exact IPv4 and IPv6 SANs, TLS 1.2/TLS 1.3, required and optional ALPN, and transport-origin binding. - Cumulative interactive-first RAM, VRAM, batch, local-model, admission, pause, and compositor-pressure mitigation plans, including active-consumer reduction at exact hard limits. -- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; can conservatively feed RSS into the resource governor; and can sample one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling (Linux Kernel Documentation, n.d.). The sampler performs no Chromium process discovery, child-process aggregation, cgroup accounting, GPU/heap measurement, or cross-platform sampling. +- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; conservatively feeds supplied RSS into the resource governor; samples one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling; and aggregates or samples an explicit process set of at most 256 unique nonzero PIDs with checked RSS addition and fail-closed all-member sampling (Linux Kernel Documentation, n.d.). These adapters perform no Chromium process discovery, child-process/cgroup attribution, GPU/heap measurement, or cross-platform sampling. - Universally value-redacted network evidence with explicit path, metadata, and provenance bounds; ambiguous path rejection; validated source URLs; lowercase SHA-256 identifiers; and verification state. - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. From 75c9b5c7ea6aacc1342afb4d49c050bdffcd1d8c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Mon, 17 Aug 2026 12:25:48 +0900 Subject: [PATCH 14/25] style(resource): apply canonical formatting after stack alignment --- crates/originweave-resource/src/lib.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index 4724f1674..f04a9dc01 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -509,9 +509,7 @@ fn parse_linux_proc_stat_identity(stat: &str) -> Result<(u32, u64), BrowserRssSa } let process_id_text = &stat[..open_comm_index]; - if process_id_text.is_empty() - || !process_id_text.bytes().all(|byte| byte.is_ascii_digit()) - { + if process_id_text.is_empty() || !process_id_text.bytes().all(|byte| byte.is_ascii_digit()) { return Err(BrowserRssSampleError::InvalidProcessStat); } let process_id = process_id_text @@ -543,9 +541,7 @@ fn parse_linux_proc_stat_identity(stat: &str) -> Result<(u32, u64), BrowserRssSa /// parentheses, so this parser anchors on the final `") "` delimiter instead /// of splitting the complete record on whitespace. Malformed or truncated /// records fail closed rather than producing a reusable PID-only identity. -pub fn parse_linux_proc_stat_start_time_ticks( - stat: &str, -) -> Result { +pub fn parse_linux_proc_stat_start_time_ticks(stat: &str) -> Result { parse_linux_proc_stat_identity(stat).map(|(_process_id, start_time_ticks)| start_time_ticks) } @@ -715,7 +711,10 @@ pub fn sample_linux_process_identity_set_rss_bytes( identities: &[LinuxProcessIdentity], ) -> Result { validate_browser_process_set_size(identities.len())?; - let process_ids: Vec = identities.iter().map(|identity| identity.process_id).collect(); + let process_ids: Vec = identities + .iter() + .map(|identity| identity.process_id) + .collect(); validate_browser_process_ids(&process_ids)?; let mut samples = Vec::with_capacity(identities.len()); From 537f4710ac0232843c919655c51831fffa2d30bf Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:36:38 +0900 Subject: [PATCH 15/25] test(resource): avoid racy identity RSS equality --- .../tests/browser_process_identity_rss.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/originweave-resource/tests/browser_process_identity_rss.rs b/crates/originweave-resource/tests/browser_process_identity_rss.rs index bbe31229c..e8a9f37be 100644 --- a/crates/originweave-resource/tests/browser_process_identity_rss.rs +++ b/crates/originweave-resource/tests/browser_process_identity_rss.rs @@ -105,11 +105,11 @@ fn linux_identity_sampler_rejects_pid_reuse_and_samples_current_process() -> Res .map_err(|error| format!("sample current process identity: {error:?}"))?; assert!(rss_bytes > 0); assert_eq!(rss_bytes % 1_024, 0); - assert_eq!( - sample_linux_process_identity_set_rss_bytes(&[identity]) - .map_err(|error| format!("sample current identity set: {error:?}"))?, - rss_bytes - ); + + let identity_set_rss_bytes = sample_linux_process_identity_set_rss_bytes(&[identity]) + .map_err(|error| format!("sample current identity set: {error:?}"))?; + assert!(identity_set_rss_bytes > 0); + assert_eq!(identity_set_rss_bytes % 1_024, 0); let stale = LinuxProcessIdentity::new( identity.process_id(), From ac22b5ccf4bb7f4e6c0719fb05a8e0819893e2e0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:41:32 +0900 Subject: [PATCH 16/25] refactor(resource): reuse tested process identity verifier --- crates/originweave-resource/src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index f04a9dc01..814df3578 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -575,11 +575,10 @@ pub fn read_linux_process_identity( { let stat = std::fs::read_to_string(format!("/proc/{process_id}/stat")) .map_err(|_error| BrowserRssSampleError::ProcessStatUnavailable)?; - let (observed_process_id, start_time_ticks) = parse_linux_proc_stat_identity(&stat)?; - if observed_process_id != process_id { - return Err(BrowserRssSampleError::ProcessIdentityChanged); - } - LinuxProcessIdentity::new(observed_process_id, start_time_ticks) + let start_time_ticks = parse_linux_proc_stat_start_time_ticks(&stat)?; + let identity = LinuxProcessIdentity::new(process_id, start_time_ticks)?; + verify_linux_process_identity(identity, &stat)?; + Ok(identity) } #[cfg(not(target_os = "linux"))] From 1bbe11640b9d135923fc8abe158058d276957d22 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:48:38 +0900 Subject: [PATCH 17/25] test(resource): expose deterministic process-identity parsing boundary --- .../tests/browser_process_identity_rss.rs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/originweave-resource/tests/browser_process_identity_rss.rs b/crates/originweave-resource/tests/browser_process_identity_rss.rs index e8a9f37be..5c7e4fba7 100644 --- a/crates/originweave-resource/tests/browser_process_identity_rss.rs +++ b/crates/originweave-resource/tests/browser_process_identity_rss.rs @@ -1,6 +1,6 @@ use originweave_resource::{ BrowserRssSampleError, LinuxProcessIdentity, parse_linux_proc_stat_start_time_ticks, - read_linux_process_identity, sample_linux_process_identity_rss_bytes, + parse_linux_process_identity, read_linux_process_identity, sample_linux_process_identity_rss_bytes, sample_linux_process_identity_set_rss_bytes, verify_linux_process_identity, }; @@ -59,6 +59,28 @@ fn proc_stat_parser_rejects_hostile_structure_and_numeric_fields() { } } +#[test] +fn process_identity_parser_binds_expected_pid_and_fails_closed() -> Result<(), BrowserRssSampleError> { + let expected = LinuxProcessIdentity::new(42, 987_654)?; + assert_eq!( + parse_linux_process_identity(42, &proc_stat_with_start_time(987_654)), + Ok(expected) + ); + assert_eq!( + parse_linux_process_identity(0, &proc_stat_with_start_time(987_654)), + Err(BrowserRssSampleError::InvalidProcessId) + ); + assert_eq!( + parse_linux_process_identity(42, &proc_stat_for_pid(43, 987_654)), + Err(BrowserRssSampleError::ProcessIdentityChanged) + ); + assert_eq!( + parse_linux_process_identity(42, "malformed"), + Err(BrowserRssSampleError::InvalidProcessStat) + ); + Ok(()) +} + #[test] fn process_identity_binds_pid_to_kernel_start_time() -> Result<(), BrowserRssSampleError> { let identity = LinuxProcessIdentity::new(42, 987_654)?; From 0505ed490d27a25ab84e5e33284b1389391acf9d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 02:52:11 +0900 Subject: [PATCH 18/25] feat(resource): add deterministic process identity parse boundary --- crates/originweave-resource/src/lib.rs | 27 ++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index 814df3578..4dfdd3298 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -545,6 +545,28 @@ pub fn parse_linux_proc_stat_start_time_ticks(stat: &str) -> Result Result { + if process_id == 0 { + return Err(BrowserRssSampleError::InvalidProcessId); + } + let (observed_process_id, start_time_ticks) = parse_linux_proc_stat_identity(stat)?; + if observed_process_id != process_id { + return Err(BrowserRssSampleError::ProcessIdentityChanged); + } + Ok(LinuxProcessIdentity { + process_id, + start_time_ticks, + }) +} + /// Verify that one Linux stat record still represents the supplied process identity. /// /// Both PID and kernel start time must match. A syntactically valid record for a @@ -575,10 +597,7 @@ pub fn read_linux_process_identity( { let stat = std::fs::read_to_string(format!("/proc/{process_id}/stat")) .map_err(|_error| BrowserRssSampleError::ProcessStatUnavailable)?; - let start_time_ticks = parse_linux_proc_stat_start_time_ticks(&stat)?; - let identity = LinuxProcessIdentity::new(process_id, start_time_ticks)?; - verify_linux_process_identity(identity, &stat)?; - Ok(identity) + parse_linux_process_identity(process_id, &stat) } #[cfg(not(target_os = "linux"))] From 70e12c408ad92ef510a9c8841c77226d7fcd587d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 18 Aug 2026 06:33:45 +0900 Subject: [PATCH 19/25] fix(resource): preserve typed identity sampling without uncovered propagation --- crates/originweave-resource/src/lib.rs | 14 ++++++++------ .../tests/browser_process_identity_rss.rs | 8 +++++--- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index 4dfdd3298..6f76955b0 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -712,12 +712,14 @@ pub fn sample_linux_process_rss_bytes(process_id: u32) -> Result Result { - let rss_bytes = sample_linux_process_rss_bytes(identity.process_id)?; - let current_identity = read_linux_process_identity(identity.process_id)?; - if current_identity != identity { - return Err(BrowserRssSampleError::ProcessIdentityChanged); - } - Ok(rss_bytes) + sample_linux_process_rss_bytes(identity.process_id).and_then(|rss_bytes| { + read_linux_process_identity(identity.process_id).and_then(|current_identity| { + if current_identity != identity { + return Err(BrowserRssSampleError::ProcessIdentityChanged); + } + Ok(rss_bytes) + }) + }) } /// Sample the aggregate RSS of one explicit bounded Linux process-identity set. diff --git a/crates/originweave-resource/tests/browser_process_identity_rss.rs b/crates/originweave-resource/tests/browser_process_identity_rss.rs index 5c7e4fba7..a15cde296 100644 --- a/crates/originweave-resource/tests/browser_process_identity_rss.rs +++ b/crates/originweave-resource/tests/browser_process_identity_rss.rs @@ -1,7 +1,8 @@ use originweave_resource::{ BrowserRssSampleError, LinuxProcessIdentity, parse_linux_proc_stat_start_time_ticks, - parse_linux_process_identity, read_linux_process_identity, sample_linux_process_identity_rss_bytes, - sample_linux_process_identity_set_rss_bytes, verify_linux_process_identity, + parse_linux_process_identity, read_linux_process_identity, + sample_linux_process_identity_rss_bytes, sample_linux_process_identity_set_rss_bytes, + verify_linux_process_identity, }; fn proc_stat_with_start_time(start_time_ticks: u64) -> String { @@ -60,7 +61,8 @@ fn proc_stat_parser_rejects_hostile_structure_and_numeric_fields() { } #[test] -fn process_identity_parser_binds_expected_pid_and_fails_closed() -> Result<(), BrowserRssSampleError> { +fn process_identity_parser_binds_expected_pid_and_fails_closed() -> Result<(), BrowserRssSampleError> +{ let expected = LinuxProcessIdentity::new(42, 987_654)?; assert_eq!( parse_linux_process_identity(42, &proc_stat_with_start_time(987_654)), From 5a6a17415eb4fa64dfa12cbdce4cda5f2a67b5fa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 20:05:14 -0700 Subject: [PATCH 20/25] test(resource): require identity check before RSS read --- .../originweave-resource/tests/browser_process_identity_rss.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/originweave-resource/tests/browser_process_identity_rss.rs b/crates/originweave-resource/tests/browser_process_identity_rss.rs index a15cde296..f97a559cd 100644 --- a/crates/originweave-resource/tests/browser_process_identity_rss.rs +++ b/crates/originweave-resource/tests/browser_process_identity_rss.rs @@ -165,7 +165,7 @@ fn linux_identity_sampler_rejects_pid_reuse_and_samples_current_process() -> Res ); assert_eq!( sample_linux_process_identity_rss_bytes(absent), - Err(BrowserRssSampleError::ProcessStatusUnavailable) + Err(BrowserRssSampleError::ProcessStatUnavailable) ); } From 4d1f1179e0e336251653fcd32c2639117589a0da Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 20:07:40 -0700 Subject: [PATCH 21/25] fix(resource): verify process identity around RSS read --- crates/originweave-resource/src/lib.rs | 33 ++++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index 6f76955b0..c2a6a75eb 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -703,23 +703,30 @@ pub fn sample_linux_process_rss_bytes(process_id: u32) -> Result Result<(), BrowserRssSampleError> { + let current_identity = read_linux_process_identity(identity.process_id)?; + if current_identity != identity { + return Err(BrowserRssSampleError::ProcessIdentityChanged); + } + Ok(()) +} + +/// Sample one Linux process RSS only while its PID names the bound process instance. /// -/// RSS is sampled first, then `/proc//stat` is read and compared with the -/// supplied kernel start-time identity. If the PID was reused at or before the -/// verification point, the measurement is discarded. The function never turns -/// this operating-system identity into Chromium/task ownership authority. +/// The sampler checks `/proc//stat` before reading `/proc//status` so +/// a stale caller identity cannot authorize inspection of a reused PID. It then +/// checks the kernel identity again after the RSS read; any disappearance or PID +/// reuse during the cross-file sample invalidates the measurement. The function +/// never turns this operating-system identity into Chromium/task ownership authority. pub fn sample_linux_process_identity_rss_bytes( identity: LinuxProcessIdentity, ) -> Result { - sample_linux_process_rss_bytes(identity.process_id).and_then(|rss_bytes| { - read_linux_process_identity(identity.process_id).and_then(|current_identity| { - if current_identity != identity { - return Err(BrowserRssSampleError::ProcessIdentityChanged); - } - Ok(rss_bytes) - }) - }) + ensure_linux_process_identity_current(identity)?; + let rss_bytes = sample_linux_process_rss_bytes(identity.process_id)?; + ensure_linux_process_identity_current(identity)?; + Ok(rss_bytes) } /// Sample the aggregate RSS of one explicit bounded Linux process-identity set. From 3f0a5775e331a3ec3e9616d0f06571c6585e2066 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 20:09:36 -0700 Subject: [PATCH 22/25] docs(resource): record identity-bounded RSS sampling --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff1bccd65..550d8e51b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Real loopback TCP integration proof plus deterministic timeout, refusal, retry, peer-inspection, peer-mismatch, canonicalization, IPv6 metadata, and single-use replay tests. - Real loopback rustls integration covering trusted DNS SAN, Common-Name fallback rejection, wrong-name and untrusted-root rejection, fixed-time expiry and not-yet-valid failures, exact IPv4 and IPv6 SANs, TLS 1.2/TLS 1.3, required and optional ALPN, and transport-origin binding. - Cumulative interactive-first RAM, VRAM, batch, local-model, admission, pause, and compositor-pressure mitigation plans, including active-consumer reduction at exact hard limits. -- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; conservatively feeds supplied RSS into the resource governor; samples one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling; and aggregates or samples an explicit process set of at most 256 unique nonzero PIDs with checked RSS addition and fail-closed all-member sampling (Linux Kernel Documentation, n.d.). These adapters perform no Chromium process discovery, child-process/cgroup attribution, GPU/heap measurement, or cross-platform sampling. +- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; conservatively feeds supplied RSS into the resource governor; samples one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling; and aggregates or samples an explicit process set of at most 256 unique nonzero PIDs with checked RSS addition and fail-closed all-member sampling (Linux Kernel Documentation, n.d.). Identity-bound RSS sampling verifies the Linux PID plus `/proc//stat` start time before reading process status and re-verifies it afterward, so stale identities cannot authorize inspection of a reused PID and cross-file PID reuse invalidates the measurement. These adapters perform no Chromium process discovery, child-process/cgroup attribution, GPU/heap measurement, or cross-platform sampling. - Universally value-redacted network evidence with explicit path, metadata, and provenance bounds; ambiguous path rejection; validated source URLs; lowercase SHA-256 identifiers; and verification state. - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. From 463166134d6a79655fcc3a81312b4546a375134e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 19 Aug 2026 20:21:18 -0700 Subject: [PATCH 23/25] fix(resource): preserve exact coverage for identity sampling --- crates/originweave-resource/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index c2a6a75eb..fb1d0637c 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -724,9 +724,9 @@ pub fn sample_linux_process_identity_rss_bytes( identity: LinuxProcessIdentity, ) -> Result { ensure_linux_process_identity_current(identity)?; - let rss_bytes = sample_linux_process_rss_bytes(identity.process_id)?; - ensure_linux_process_identity_current(identity)?; - Ok(rss_bytes) + sample_linux_process_rss_bytes(identity.process_id).and_then(|rss_bytes| { + ensure_linux_process_identity_current(identity).map(|()| rss_bytes) + }) } /// Sample the aggregate RSS of one explicit bounded Linux process-identity set. From 8969e76a911a379aa69125dad70c6513115ddcaa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 21 Aug 2026 07:07:34 +0900 Subject: [PATCH 24/25] style(resource): apply rustfmt --- crates/originweave-resource/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/originweave-resource/src/lib.rs b/crates/originweave-resource/src/lib.rs index fb1d0637c..22a2f5468 100644 --- a/crates/originweave-resource/src/lib.rs +++ b/crates/originweave-resource/src/lib.rs @@ -724,9 +724,8 @@ pub fn sample_linux_process_identity_rss_bytes( identity: LinuxProcessIdentity, ) -> Result { ensure_linux_process_identity_current(identity)?; - sample_linux_process_rss_bytes(identity.process_id).and_then(|rss_bytes| { - ensure_linux_process_identity_current(identity).map(|()| rss_bytes) - }) + sample_linux_process_rss_bytes(identity.process_id) + .and_then(|rss_bytes| ensure_linux_process_identity_current(identity).map(|()| rss_bytes)) } /// Sample the aggregate RSS of one explicit bounded Linux process-identity set. From 15713ac15e52cc88369af2c4b50abd86fce869b9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 26 Aug 2026 03:15:14 -0700 Subject: [PATCH 25/25] docs(resource): preserve process-set RSS changelog after stack refresh --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 047c9e72c..c926cb8e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,7 +27,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - Real loopback TCP integration proof plus deterministic timeout, refusal, retry, peer-inspection, peer-mismatch, canonicalization, IPv6 metadata, and single-use replay tests. - Real loopback rustls integration covering trusted DNS SAN, Common-Name fallback rejection, wrong-name and untrusted-root rejection, fixed-time expiry and not-yet-valid failures, exact IPv4 and IPv6 SANs, TLS 1.2/TLS 1.3, required and optional ALPN, and transport-origin binding. - Cumulative interactive-first RAM, VRAM, batch, local-model, admission, pause, and compositor-pressure mitigation plans, including active-consumer reduction at exact hard limits. -- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; can conservatively feed RSS into the resource governor; and can sample one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling (Linux Kernel Documentation, n.d.). The sampler performs no Chromium process discovery, child-process aggregation, cgroup accounting, GPU/heap measurement, or cross-platform sampling. +- Bounded browser-task runtime telemetry that validates platform-supplied RSS, semantic-observation bytes, governed-action latency, and total task duration; conservatively feeds supplied RSS into the resource governor; samples one explicitly supplied Linux process ID from `/proc//status` with strict `VmRSS` syntax, unit, duplicate, overflow, and read-failure handling; and aggregates or samples an explicit process set of at most 256 unique nonzero PIDs with checked RSS addition and fail-closed all-member sampling (Linux Kernel Documentation, n.d.). Identity-bound RSS sampling verifies the Linux PID plus `/proc//stat` start time before reading process status and re-verifies it afterward, so stale identities cannot authorize inspection of a reused PID and cross-file PID reuse invalidates the measurement. These adapters perform no Chromium process discovery, child-process/cgroup attribution, GPU/heap measurement, or cross-platform sampling. - Universally value-redacted network evidence with explicit path, metadata, and provenance bounds; ambiguous path rejection; validated source URLs; lowercase SHA-256 identifiers; and verification state. - Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement. - Hourly bounded OpenCode product-development workflow using `NVIDIA_NIM_API_KEY`, an unprivileged disposable workspace, loopback-only model broker, independently verified patches, and publication through a dedicated `OPENCODE_PR_TOKEN` that cannot review or merge. @@ -94,4 +94,4 @@ All notable changes to OriginWeave are documented in this file. The format follo Linux Kernel Documentation. (n.d.). *The /proc filesystem*. Retrieved August 15, 2026, from https://www.kernel.org/doc/html/latest/filesystems/proc.html -[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD \ No newline at end of file +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD