Skip to content
Draft
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Cumulative interactive-first RAM, VRAM, batch, local-model, admission, pause, and compositor-pressure mitigation plans, including active-consumer reduction at exact hard limits.
- 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.
- Credential-safe verified action-outcome evidence that binds action kind, canonical target origin, complete action-intent digest, bounded browser post-condition kind, same-clock dispatch and observation timestamps, and exact provenance; construction rejects unverified or rejected post-conditions and any observation timestamp that predates action dispatch while allowing equal coarse-clock ticks.
- Credential-safe structured-value evidence that binds a bounded field identifier and lowercase SHA-256 value digest to one exact OriginWeave node plus independently verified same-origin DOM/accessibility and network-response provenance, without carrying the raw extracted value.
- 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.
- Architecture, agent, security, contribution, research, database naming, roadmap, quality-gate, and TLS service-identity ADR documentation.
Expand Down
138 changes: 137 additions & 1 deletion crates/originweave-evidence/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use sensitive_access::{

use std::collections::BTreeMap;

use originweave_core::Origin;
use originweave_core::{ObservedNodeHandle, Origin};

const REDACTED: &str = "[REDACTED]";

Expand All @@ -37,6 +37,8 @@ pub const MAX_METADATA_NAME_BYTES: usize = 256;
pub const MAX_METADATA_VALUE_BYTES: usize = 8_192;
/// Maximum source URL or source-locator size retained in provenance metadata.
pub const MAX_PROVENANCE_TEXT_BYTES: usize = 8_192;
/// Maximum byte length of one structured extracted-field identifier.
pub const MAX_STRUCTURED_FIELD_NAME_BYTES: usize = 128;

/// An HTTP method recorded for network evidence.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down Expand Up @@ -365,3 +367,137 @@ fn valid_sha256(source_hash: &str) -> bool {
.bytes()
.all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
}

/// A credential-safe proof bundle for one extracted structured value.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructuredValueEvidence {
field_name: String,
value_hash: String,
source_node: ObservedNodeHandle,
node_provenance: ProvenanceRecord,
network_provenance: ProvenanceRecord,
}

impl StructuredValueEvidence {
/// Bind one structured field digest to exact node and network provenance.
pub fn new(
field_name: &str,
value_hash: &str,
source_node: ObservedNodeHandle,
node_provenance: ProvenanceRecord,
network_provenance: ProvenanceRecord,
) -> Result<Self, StructuredValueEvidenceError> {
if !valid_structured_field_name(field_name) {
return Err(StructuredValueEvidenceError::InvalidFieldName);
}
if !valid_sha256(value_hash) {
return Err(StructuredValueEvidenceError::InvalidValueHash);
}
if node_provenance.verification_result() != VerificationResult::Verified {
return Err(StructuredValueEvidenceError::NodeProvenanceNotVerified);
}
if !matches!(
node_provenance.source_kind(),
EvidenceSourceKind::DomTree | EvidenceSourceKind::AccessibilityTree
) {
return Err(StructuredValueEvidenceError::NodeProvenanceKindMismatch);
}
if network_provenance.verification_result() != VerificationResult::Verified {
return Err(StructuredValueEvidenceError::NetworkProvenanceNotVerified);
}
if network_provenance.source_kind() != EvidenceSourceKind::NetworkResponse {
return Err(StructuredValueEvidenceError::NetworkProvenanceKindMismatch);
}
if node_provenance.source_origin() != source_node.origin()
|| network_provenance.source_origin() != source_node.origin()
{
return Err(StructuredValueEvidenceError::SourceOriginMismatch);
}
Ok(Self {
field_name: field_name.to_owned(),
value_hash: value_hash.to_owned(),
source_node,
node_provenance,
network_provenance,
})
}

/// Return the bounded structured field identifier.
#[must_use]
pub fn field_name(&self) -> &str {
&self.field_name
}

/// Return the lowercase SHA-256 digest of the canonical extracted value bytes.
#[must_use]
pub fn value_hash(&self) -> &str {
&self.value_hash
}

/// Return the exact OriginWeave-owned source node.
#[must_use]
pub const fn source_node(&self) -> &ObservedNodeHandle {
&self.source_node
}

/// Return the independently verified DOM/accessibility provenance for the source node.
#[must_use]
pub const fn node_provenance(&self) -> &ProvenanceRecord {
&self.node_provenance
}

/// Return the independently verified network provenance associated with the value.
#[must_use]
pub const fn network_provenance(&self) -> &ProvenanceRecord {
&self.network_provenance
}
}

/// A fail-closed reason why structured extraction evidence could not be constructed.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructuredValueEvidenceError {
/// The structured field identifier was empty, oversized, or contained unsupported bytes.
InvalidFieldName,
/// The value digest was not a canonical lowercase SHA-256 identifier.
InvalidValueHash,
/// The node provenance did not carry independent verified status.
NodeProvenanceNotVerified,
/// The node provenance was not a DOM or accessibility observation.
NodeProvenanceKindMismatch,
/// The network provenance did not carry independent verified status.
NetworkProvenanceNotVerified,
/// The network provenance was not a structured network-response observation.
NetworkProvenanceKindMismatch,
/// Node or network provenance belonged to a different canonical origin than the source node.
SourceOriginMismatch,
}

impl std::fmt::Display for StructuredValueEvidenceError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(match self {
Self::InvalidFieldName => "invalid structured field name",
Self::InvalidValueHash => "invalid structured value hash",
Self::NodeProvenanceNotVerified => "node provenance is not verified",
Self::NodeProvenanceKindMismatch => {
"node provenance is not DOM or accessibility evidence"
}
Self::NetworkProvenanceNotVerified => "network provenance is not verified",
Self::NetworkProvenanceKindMismatch => {
"network provenance is not network-response evidence"
}
Self::SourceOriginMismatch => "provenance origin does not match source node origin",
})
}
}

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

fn valid_structured_field_name(field_name: &str) -> bool {
if field_name.is_empty() || field_name.len() > MAX_STRUCTURED_FIELD_NAME_BYTES {
return false;
}
field_name
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
&& field_name.bytes().any(|byte| byte.is_ascii_alphanumeric())
}
Loading
Loading