Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8b1ae11
test(resource): define bounded browser process-set RSS contract
seonghobae Aug 11, 2026
65e0cb7
test(resource): apply canonical process-set test formatting
seonghobae Aug 11, 2026
25d288d
feat(resource): aggregate bounded browser process-set RSS
seonghobae Aug 11, 2026
61ddf7b
style(resource): apply canonical process-set formatting
seonghobae Aug 11, 2026
8958b55
test(resource): avoid racy sequential RSS comparison
seonghobae Aug 11, 2026
986958a
docs(resource): record bounded process-set RSS sampling
seonghobae Aug 11, 2026
8375d81
fix(stack): reconcile process-set RSS with live telemetry head
seonghobae Aug 15, 2026
a82626d
fix(resource): preserve strict VmRSS syntax after stack reconcile
seonghobae Aug 15, 2026
29a410c
docs(resource): record bounded process-set RSS on live stack
seonghobae Aug 15, 2026
cd8f418
test(resource): bind RSS sampling to Linux process identity
seonghobae Aug 16, 2026
7b39fa5
style(resource): apply canonical identity test formatting
seonghobae Aug 16, 2026
922861b
feat(resource): bind RSS sampling to Linux process identity
seonghobae Aug 16, 2026
bfff435
test(resource): exhaust hostile Linux process identity cases
seonghobae Aug 16, 2026
2ef52e6
Merge current resource telemetry prerequisite into process-set RSS
seonghobae Aug 17, 2026
dd0d1b3
docs: preserve process-set RSS changelog after stack alignment
seonghobae Aug 17, 2026
75c9b5c
style(resource): apply canonical formatting after stack alignment
seonghobae Aug 17, 2026
537f471
test(resource): avoid racy identity RSS equality
seonghobae Aug 17, 2026
ac22b5c
refactor(resource): reuse tested process identity verifier
seonghobae Aug 17, 2026
1bbe116
test(resource): expose deterministic process-identity parsing boundary
seonghobae Aug 17, 2026
0505ed4
feat(resource): add deterministic process identity parse boundary
seonghobae Aug 17, 2026
70e12c4
fix(resource): preserve typed identity sampling without uncovered pro…
seonghobae Aug 17, 2026
5a6a174
test(resource): require identity check before RSS read
seonghobae Aug 20, 2026
4d1f117
fix(resource): verify process identity around RSS read
seonghobae Aug 20, 2026
3f0a577
docs(resource): record identity-bounded RSS sampling
seonghobae Aug 20, 2026
4631661
fix(resource): preserve exact coverage for identity sampling
seonghobae Aug 20, 2026
8969e76
style(resource): apply rustfmt
seonghobae Aug 20, 2026
03eda19
merge(stack): refresh browser process-set RSS prerequisite
seonghobae Aug 26, 2026
15713ac
docs(resource): preserve process-set RSS changelog after stack refresh
seonghobae Aug 26, 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
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<pid>/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/<pid>/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/<pid>/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.
Expand Down Expand Up @@ -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
[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD
293 changes: 293 additions & 0 deletions crates/originweave-resource/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -402,6 +405,20 @@ 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 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.
Expand All @@ -418,6 +435,204 @@ 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(())
}

/// A Linux PID bound to the kernel start-time tick recorded in `/proc/<pid>/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<Self, BrowserRssSampleError> {
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/<pid>/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::<u32>()
.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::<u64>()
.map_err(|_error| BrowserRssSampleError::InvalidProcessStat)?;
Ok((process_id, start_time_ticks))
}

/// Parse Linux `/proc/<pid>/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<u64, BrowserRssSampleError> {
parse_linux_proc_stat_identity(stat).map(|(_process_id, start_time_ticks)| start_time_ticks)
}

/// Parse one Linux stat record and bind it to the caller-requested process identifier.
///
/// The stat record must be structurally valid and its embedded PID must equal
/// `process_id`; otherwise this boundary fails closed. It performs no process
/// discovery and does not prove Chromium/task ownership.
pub fn parse_linux_process_identity(
process_id: u32,
stat: &str,
) -> Result<LinuxProcessIdentity, BrowserRssSampleError> {
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
/// 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/<pid>/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<LinuxProcessIdentity, BrowserRssSampleError> {
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)?;
parse_linux_process_identity(process_id, &stat)
}

#[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
/// 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<u64, BrowserRssSampleError> {
validate_browser_process_set_size(samples.len())?;
let process_ids: Vec<u32> = 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/<pid>/status` and return the exact `VmRSS` value in bytes.
///
/// Linux reports `VmRSS` in `kB`, where the kernel ABI uses 1024-byte units.
Expand Down Expand Up @@ -487,3 +702,81 @@ pub fn sample_linux_process_rss_bytes(process_id: u32) -> Result<u64, BrowserRss
Err(BrowserRssSampleError::UnsupportedPlatform)
}
}

fn ensure_linux_process_identity_current(
identity: LinuxProcessIdentity,
) -> 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.
///
/// The sampler checks `/proc/<pid>/stat` before reading `/proc/<pid>/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<u64, BrowserRssSampleError> {
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 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<u64, BrowserRssSampleError> {
validate_browser_process_set_size(identities.len())?;
let process_ids: Vec<u32> = 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
/// the complete supplied set before any operating-system read, samples every
/// exact member, fails closed if any member cannot be sampled, and returns no
/// partial total. It does not walk child processes or cgroups and does not prove
/// that the supplied identifiers belong to Chromium or to the same Agent Task.
pub fn sample_linux_process_set_rss_bytes(
process_ids: &[u32],
) -> Result<u64, BrowserRssSampleError> {
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)
}
}
Loading
Loading