Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Credential-free connection and redirect evidence containing canonical addresses, destination classes, target digests, hop numbers, and approved-address counts.
- Credential-free verified TCP evidence containing the logical origin, requested socket, observed peer, destination class, successful attempt number, and per-attempt timeout.
- Standard `Display` and `std::error::Error` contracts for destination, redirect, digest, direct-network, and TLS failures, including preserved destination-policy, rustls, and operating-system sources where applicable.
- Standard `Display` and `std::error::Error` contract for policy denial reasons, with deterministic credential-free messages and no nested sources (The Rust Project Developers, 2026a, 2026b).
- 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.
Expand Down Expand Up @@ -75,4 +76,10 @@ 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

The Rust Project Developers. (2026a). *Display in std::fmt* (Rust 1.97.1 API documentation). https://doc.rust-lang.org/1.97.1/std/fmt/trait.Display.html

The Rust Project Developers. (2026b). *Error in std::error* (Rust 1.97.1 API documentation). https://doc.rust-lang.org/1.97.1/std/error/trait.Error.html

[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD
35 changes: 35 additions & 0 deletions crates/originweave-policy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub use sensitive_data::{
evaluate_handle_use,
};

use std::fmt;

use originweave_core::{
ActionRequest, ApprovalEvidence, ApprovalScope, Capability, ExecutionPurpose,
InstructionSource, PolicyContext, RiskClass, RobotsDecision, SecretDelivery, SessionMode,
Expand Down Expand Up @@ -66,6 +68,39 @@ pub enum DenialReason {
ApprovalScopeMismatch,
}

impl fmt::Display for DenialReason {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let message = match self {
Self::HumanModeNotAgentControlled => {
"human mode does not grant autonomous agent control"
}
Self::ModePurposeMismatch => "session mode and execution purpose are incompatible",
Self::UntrustedInstructionSource => {
"untrusted web content cannot authorize this action"
}
Self::MissingCapability(_) => "required action capability is missing",
Self::OriginNotReadable => "target origin is not readable under the current policy",
Self::CrawlerMutation => "crawler mode forbids state-mutating actions",
Self::CrossOriginMutation => {
"cross-origin mutation requires separately authorized source and target origins"
}
Self::OriginNotWritable => "target origin is not writable under the current policy",
Self::RobotsDisallowed => "robots policy denies this public crawl",
Self::RobotsUnknown => "robots policy is unknown for this public crawl",
Self::RobotsNotApplicable => {
"public crawl requires an applicable robots policy decision"
}
Self::SecretBrokerRequired => "secret-bearing actions require opaque broker delivery",
Self::UnexpectedSecretMaterial => "non-secret action cannot carry secret material",
Self::ForbiddenRisk => "action risk class is not delegable",
Self::ApprovalScopeMismatch => "approval evidence does not authorize this action scope",
};
formatter.write_str(message)
}
}

impl std::error::Error for DenialReason {}

/// Evaluate a typed browser action against one explicit policy context.
#[must_use]
pub fn evaluate(request: &ActionRequest, context: &PolicyContext) -> Decision {
Expand Down
79 changes: 79 additions & 0 deletions crates/originweave-policy/tests/denial_reason_error_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use std::error::Error;

use originweave_core::Capability;
use originweave_policy::DenialReason;

fn assert_source_free(error: &(dyn Error + 'static)) {
assert!(error.source().is_none());
}

#[test]
fn denial_reasons_expose_stable_standard_error_contracts() {
let cases = [
(
DenialReason::HumanModeNotAgentControlled,
"human mode does not grant autonomous agent control",
),
(
DenialReason::ModePurposeMismatch,
"session mode and execution purpose are incompatible",
),
(
DenialReason::UntrustedInstructionSource,
"untrusted web content cannot authorize this action",
),
(
DenialReason::MissingCapability(Capability::Navigate),
"required action capability is missing",
),
(
DenialReason::OriginNotReadable,
"target origin is not readable under the current policy",
),
(
DenialReason::CrawlerMutation,
"crawler mode forbids state-mutating actions",
),
(
DenialReason::CrossOriginMutation,
"cross-origin mutation requires separately authorized source and target origins",
),
(
DenialReason::OriginNotWritable,
"target origin is not writable under the current policy",
),
(
DenialReason::RobotsDisallowed,
"robots policy denies this public crawl",
),
(
DenialReason::RobotsUnknown,
"robots policy is unknown for this public crawl",
),
(
DenialReason::RobotsNotApplicable,
"public crawl requires an applicable robots policy decision",
),
(
DenialReason::SecretBrokerRequired,
"secret-bearing actions require opaque broker delivery",
),
(
DenialReason::UnexpectedSecretMaterial,
"non-secret action cannot carry secret material",
),
(
DenialReason::ForbiddenRisk,
"action risk class is not delegable",
),
(
DenialReason::ApprovalScopeMismatch,
"approval evidence does not authorize this action scope",
),
];

for (reason, expected) in cases {
assert_eq!(reason.to_string(), expected);
assert_source_free(&reason);
}
}
Loading