Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3bd854f
test(resource): require bounded browser task telemetry
seonghobae Aug 10, 2026
b4790c3
test(resource): format browser task telemetry contract
seonghobae Aug 10, 2026
612a4ec
feat(resource): add bounded browser task telemetry
seonghobae Aug 10, 2026
1c85b96
docs(changelog): record browser task telemetry
seonghobae Aug 10, 2026
f1ea560
test(resource): require measured browser RSS governor input
seonghobae Aug 10, 2026
2f2ea81
style(resource): apply canonical telemetry test formatting
seonghobae Aug 10, 2026
dc1f213
feat(resource): feed measured browser RSS into governor
seonghobae Aug 10, 2026
65dfb38
docs(changelog): record measured RSS governor input
seonghobae Aug 10, 2026
45e2c23
fix(resource): satisfy strict multiple-of lint
seonghobae Aug 10, 2026
53891b7
docs(resource): remove telemetry provenance overclaim
seonghobae Aug 10, 2026
b3f53e0
docs(changelog): state telemetry provenance precisely
seonghobae Aug 10, 2026
6f99a21
test(resource): require real Linux RSS sampling boundary
seonghobae Aug 10, 2026
1185805
test(resource): cover malformed Linux RSS records
seonghobae Aug 10, 2026
a336078
feat(resource): sample Linux browser RSS from proc status
seonghobae Aug 10, 2026
2d91579
style(resource): apply canonical RSS test formatting
seonghobae Aug 10, 2026
dab26e4
docs(resource): record bounded Linux RSS sampling
seonghobae Aug 10, 2026
b4d2e2a
Merge branch 'main' into feat/browser-task-resource-telemetry
opencode-agent[bot] Aug 13, 2026
cc6c330
test(resource): reject signed VmRSS values
seonghobae Aug 13, 2026
ce6b955
fix(resource): reject non-digit VmRSS values
seonghobae Aug 13, 2026
c2834d5
test(resource): cover VmRSS integer overflow
seonghobae Aug 13, 2026
c7e0de0
docs(resource): cite Linux RSS sampling contract
seonghobae Aug 14, 2026
f0fda66
docs(resource): define browser RSS authority boundary
seonghobae Aug 14, 2026
ed01297
Merge branch 'main' into feat/browser-task-resource-telemetry
seonghobae Aug 16, 2026
29e3de8
Merge branch 'main' into feat/browser-task-resource-telemetry
opencode-agent[bot] Aug 24, 2026
cb95ffa
docs(doctoring): record Linux VmRSS ABI decision
seonghobae Aug 24, 2026
010b923
test(docs): require consistent Linux proc citation identity
seonghobae Aug 24, 2026
6b1595e
docs(doctoring): align Linux proc retrieval identity
seonghobae Aug 24, 2026
8135cc8
Merge remote-tracking branch 'origin/main' into feat/browser-task-res…
seonghobae Aug 27, 2026
744892c
merge(main): reconcile browser resource telemetry
seonghobae Aug 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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.
- 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.
- Versioned schema-bound extraction contracts with bounded identifiers and field counts, typed value/cardinality metadata, explicit duplicate-free reviewed source channels, fail-closed schema validation, and deterministic `Display`/`std::error::Error` contracts for public schema failures.
- Rust 1.97.1 build contract, strict Clippy and rustdoc gates, and exact production function, line, region, and branch coverage enforcement.
Expand Down Expand Up @@ -78,7 +79,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- R3 and R4 approvals are bound to the exact action, target origin, and immutable digest of the complete canonical action intent; R5 legal consent is non-delegable.
- Shortened, integer, hexadecimal, and legacy octal-looking IPv4 host spellings are rejected so the policy origin cannot diverge from Chromium host interpretation.
- IPv4-mapped IPv6 is canonicalized before destination classification and pin comparison so mapped private or loopback addresses cannot bypass IPv4 policy.
- The default destination policy permits only public addresses and denies unspecified, loopback, private, shared, link-local, metadata, documentation, benchmarking, multicast, broadcast, transition, and protocol-reserved destinations.
- The default destination policy permits only public addresses and denies unspecified, loopback, private, shared, link-local, metadata, documentation, benchmarking, multicast, broadcast, transition, unallocated, and protocol-reserved destinations.
- Azure platform IP `168.63.129.16` and Amazon EKS Pod Identity endpoints `169.254.170.23` and `fd00:ec2::23` are classified as metadata or platform services before broader public, link-local, or unique-local rules.
- Resolver answers are rejected when empty or larger than 256 addresses, preventing an unbounded resolver response from entering policy state.
- `localhost` may approve only loopback addresses, while literal IPv4 and IPv6 origins may approve only the exact canonical address encoded in the origin.
Expand All @@ -102,4 +103,8 @@ 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.

### References

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
218 changes: 218 additions & 0 deletions crates/originweave-resource/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@

use std::fmt;

const MEBIBYTE_BYTES: u64 = 1_048_576;

const fn bytes_to_mebibytes_ceil(bytes: u64) -> u64 {
let whole_mebibytes = bytes / MEBIBYTE_BYTES;
if bytes.is_multiple_of(MEBIBYTE_BYTES) {
whole_mebibytes
} else {
whole_mebibytes + 1
}
}
Comment thread
seonghobae marked this conversation as resolved.

/// A validation error in a resource budget.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BudgetError {
Expand Down Expand Up @@ -148,6 +159,32 @@ impl ResourceSnapshot {
cpu_threads_in_use,
}
}

/// Build a governor snapshot using adapter-supplied browser/task RSS telemetry.
///
/// Supplied browser RSS bytes are rounded up to mebibytes so a partial
/// mebibyte is never understated at a configured pressure boundary. VRAM,
/// batch size, local-model state, frame time, and CPU-worker use remain
/// explicit adapter observations; this conversion does not infer any of
/// them from browser telemetry.
#[must_use]
pub const fn from_browser_task_telemetry(
telemetry: BrowserTaskTelemetry,
vram_mebibytes: u64,
agent_batch_size: u32,
local_model_loaded: bool,
frame_time_milliseconds: u16,
cpu_threads_in_use: u16,
) -> Self {
Self::new(
bytes_to_mebibytes_ceil(telemetry.browser_rss_bytes()),
vram_mebibytes,
agent_batch_size,
local_model_loaded,
frame_time_milliseconds,
cpu_threads_in_use,
)
}
Comment thread
seonghobae marked this conversation as resolved.
}

/// Independent mitigations that a platform adapter applies to one workload.
Expand Down Expand Up @@ -284,3 +321,184 @@ impl ResourceGovernor {
)
}
}

/// Validated browser-task resource measurements supplied by a platform adapter.
///
/// The producer is responsible for obtaining these values from its own trusted
/// measurement boundary. This value type validates relationships and bounds only;
/// it does not sample the operating system or Chromium or prove measurement
/// provenance. It stores no page content, credentials, GPU state, model identity,
/// or persistence metadata and never infers local-AI usage.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BrowserTaskTelemetry {
browser_rss_bytes: u64,
observation_bytes: u64,
action_latency_milliseconds: u64,
task_duration_milliseconds: u64,
}

impl BrowserTaskTelemetry {
/// Validate one bounded platform-supplied browser-task telemetry record.
///
/// Browser RSS and total task duration must be nonzero. An empty semantic
/// observation is valid. Action latency may be zero, but cannot exceed the
/// total task duration reported for the same execution interval.
pub const fn new(
browser_rss_bytes: u64,
observation_bytes: u64,
action_latency_milliseconds: u64,
task_duration_milliseconds: u64,
) -> Result<Self, BrowserTaskTelemetryError> {
if browser_rss_bytes == 0 {
return Err(BrowserTaskTelemetryError::ZeroBrowserRss);
}
Comment thread
seonghobae marked this conversation as resolved.
if task_duration_milliseconds == 0 {
return Err(BrowserTaskTelemetryError::ZeroTaskDuration);
}
if action_latency_milliseconds > task_duration_milliseconds {
return Err(
BrowserTaskTelemetryError::ActionLatencyExceedsTaskDuration {
action_latency_milliseconds,
task_duration_milliseconds,
},
);
}
Ok(Self {
browser_rss_bytes,
observation_bytes,
action_latency_milliseconds,
task_duration_milliseconds,
})
}

/// Return the supplied resident-set size for the browser/task process set.
#[must_use]
pub const fn browser_rss_bytes(self) -> u64 {
self.browser_rss_bytes
}

/// Return the supplied number of bytes in the bounded semantic observation.
#[must_use]
pub const fn observation_bytes(self) -> u64 {
self.observation_bytes
}

/// Return the supplied latency of the governed browser action.
#[must_use]
pub const fn action_latency_milliseconds(self) -> u64 {
self.action_latency_milliseconds
}

/// Return the supplied duration of the complete browser-task interval.
#[must_use]
pub const fn task_duration_milliseconds(self) -> u64 {
self.task_duration_milliseconds
}
}

/// A reason that browser-task telemetry cannot enter the trusted resource record.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrowserTaskTelemetryError {
/// Browser/task resident-set size was zero and therefore unusable.
ZeroBrowserRss,
/// Total task duration was zero and therefore not a usable interval.
ZeroTaskDuration,
/// One action was reported as taking longer than the enclosing task interval.
ActionLatencyExceedsTaskDuration {
/// Reported governed-action latency in milliseconds.
action_latency_milliseconds: u64,
/// Reported complete task duration in milliseconds.
task_duration_milliseconds: u64,
},
}

/// A reason that a Linux process resident-set-size sample could not be obtained safely.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BrowserRssSampleError {
/// Process identifiers are one-based and zero was supplied.
InvalidProcessId,
/// 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.
MissingVmRss,
/// More than one resident-set-size field was present in the status record.
DuplicateVmRss,
/// The resident-set-size field was syntactically malformed.
InvalidVmRss,
/// The resident-set-size field did not use the Linux kernel `kB` unit.
UnsupportedVmRssUnit,
/// Converting the kernel kibibyte count to bytes would overflow `u64`.
VmRssOverflow,
/// The current operating system does not expose Linux `/proc` process status.
UnsupportedPlatform,
}

/// 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.
/// The parser accepts exactly one `VmRSS:` record with one integer and the
/// literal `kB` unit, rejects ambiguous duplicates or trailing fields, and uses
/// checked multiplication so an untrusted status payload cannot wrap the byte count.
Comment thread
seonghobae marked this conversation as resolved.
pub fn parse_linux_proc_status_rss_bytes(status: &str) -> Result<u64, BrowserRssSampleError> {
let mut resident_kibibytes = None;

for line in status.lines() {
let Some(value_text) = line.strip_prefix("VmRSS:") else {
continue;
};
if resident_kibibytes.is_some() {
return Err(BrowserRssSampleError::DuplicateVmRss);
}
Comment thread
seonghobae marked this conversation as resolved.

let mut fields = value_text.split_whitespace();
let Some(raw_value) = fields.next() else {
return Err(BrowserRssSampleError::InvalidVmRss);
};
let Some(unit) = fields.next() else {
return Err(BrowserRssSampleError::InvalidVmRss);
};
if fields.next().is_some() {
return Err(BrowserRssSampleError::InvalidVmRss);
}
if unit != "kB" {
return Err(BrowserRssSampleError::UnsupportedVmRssUnit);
}
if !raw_value.bytes().all(|byte| byte.is_ascii_digit()) {
return Err(BrowserRssSampleError::InvalidVmRss);
}
let parsed = raw_value
.parse::<u64>()
.map_err(|_error| BrowserRssSampleError::InvalidVmRss)?;
Comment thread
seonghobae marked this conversation as resolved.
resident_kibibytes = Some(parsed);
}

let resident_kibibytes = resident_kibibytes.ok_or(BrowserRssSampleError::MissingVmRss)?;
resident_kibibytes
.checked_mul(1_024)
.ok_or(BrowserRssSampleError::VmRssOverflow)
}
Comment thread
seonghobae marked this conversation as resolved.

/// Sample one operating-system process resident set in bytes from Linux `/proc`.
///
/// The caller supplies the exact browser process identifier it owns. This
/// function performs no process discovery and follows no browser-child tree;
/// it only reads `/proc/<pid>/status` for that identifier. Non-Linux platforms
/// fail closed with [`BrowserRssSampleError::UnsupportedPlatform`].
pub fn sample_linux_process_rss_bytes(process_id: u32) -> Result<u64, BrowserRssSampleError> {
if process_id == 0 {
return Err(BrowserRssSampleError::InvalidProcessId);
}

#[cfg(target_os = "linux")]
{
let status = std::fs::read_to_string(format!("/proc/{process_id}/status"))
.map_err(|_error| BrowserRssSampleError::ProcessStatusUnavailable)?;
parse_linux_proc_status_rss_bytes(&status)
}

#[cfg(not(target_os = "linux"))]
{
let _ = process_id;
Err(BrowserRssSampleError::UnsupportedPlatform)
}
}
Loading
Loading