From 91297cd3b69f1f4c1f2247ae6b0f9d3f3bc183f0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 07:12:48 +0900 Subject: [PATCH 1/9] test(evidence): require verified post-condition for action success --- .../tests/verified_action_outcome.rs | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 crates/originweave-evidence/tests/verified_action_outcome.rs diff --git a/crates/originweave-evidence/tests/verified_action_outcome.rs b/crates/originweave-evidence/tests/verified_action_outcome.rs new file mode 100644 index 00000000..c455e699 --- /dev/null +++ b/crates/originweave-evidence/tests/verified_action_outcome.rs @@ -0,0 +1,95 @@ +#![allow(clippy::expect_used)] + +use originweave_core::{ActionIntentDigest, ActionKind, Origin}; +use originweave_evidence::{ + EvidenceSourceKind, PostConditionKind, ProvenanceRecord, VerificationResult, + VerifiedActionOutcomeError, VerifiedActionOutcomeEvidence, +}; + +const VALID_INTENT: &str = + "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; +const VALID_SOURCE_HASH: &str = + "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"; + +fn intent() -> ActionIntentDigest { + ActionIntentDigest::parse(VALID_INTENT).expect("valid intent digest") +} + +fn origin() -> Origin { + Origin::parse("https://app.example").expect("valid test origin") +} + +fn provenance(result: VerificationResult) -> ProvenanceRecord { + ProvenanceRecord::new( + "https://app.example/receipt", + "dom:#receipt-status", + VALID_SOURCE_HASH, + EvidenceSourceKind::DomTree, + result, + ) + .expect("valid provenance") +} + +#[test] +fn verified_post_condition_can_create_action_success_evidence() { + let target = origin(); + let evidence = VerifiedActionOutcomeEvidence::new( + ActionKind::Submit, + target.clone(), + intent(), + PostConditionKind::NodeStateChanged, + provenance(VerificationResult::Verified), + ) + .expect("verified post-condition should admit success evidence"); + + assert_eq!(evidence.action(), ActionKind::Submit); + assert_eq!(evidence.target_origin(), &target); + assert_eq!(evidence.intent_digest(), &intent()); + assert_eq!(evidence.post_condition(), PostConditionKind::NodeStateChanged); + assert_eq!( + evidence.provenance().verification_result(), + VerificationResult::Verified + ); +} + +#[test] +fn unverified_or_rejected_post_condition_cannot_be_recorded_as_success() { + for result in [VerificationResult::Unverified, VerificationResult::Rejected] { + let error = VerifiedActionOutcomeEvidence::new( + ActionKind::Submit, + origin(), + intent(), + PostConditionKind::NodeStateChanged, + provenance(result), + ) + .expect_err("non-verified post-condition must fail closed"); + + assert_eq!(error, VerifiedActionOutcomeError::PostConditionNotVerified); + assert_eq!( + error.to_string(), + "action success requires an independently verified post-condition" + ); + assert!(std::error::Error::source(&error).is_none()); + } +} + +#[test] +fn post_condition_kinds_cover_first_browser_vertical_slice_evidence() { + for kind in [ + PostConditionKind::UrlChanged, + PostConditionKind::NodeStateChanged, + PostConditionKind::DialogStateChanged, + PostConditionKind::NetworkMutationObserved, + ] { + let evidence = VerifiedActionOutcomeEvidence::new( + ActionKind::Submit, + origin(), + intent(), + kind, + provenance(VerificationResult::Verified), + ) + .expect("supported post-condition should admit verified evidence"); + + assert_eq!(evidence.post_condition(), kind); + } +} From e3b1e6be35c7c8855ac8e41a3e2c68d7deaf6313 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 07:14:30 +0900 Subject: [PATCH 2/9] style(evidence): apply canonical test formatting --- crates/originweave-evidence/tests/verified_action_outcome.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/originweave-evidence/tests/verified_action_outcome.rs b/crates/originweave-evidence/tests/verified_action_outcome.rs index c455e699..224725f6 100644 --- a/crates/originweave-evidence/tests/verified_action_outcome.rs +++ b/crates/originweave-evidence/tests/verified_action_outcome.rs @@ -45,7 +45,10 @@ fn verified_post_condition_can_create_action_success_evidence() { assert_eq!(evidence.action(), ActionKind::Submit); assert_eq!(evidence.target_origin(), &target); assert_eq!(evidence.intent_digest(), &intent()); - assert_eq!(evidence.post_condition(), PostConditionKind::NodeStateChanged); + assert_eq!( + evidence.post_condition(), + PostConditionKind::NodeStateChanged + ); assert_eq!( evidence.provenance().verification_result(), VerificationResult::Verified From ea069f26048e5e093649b17c76a63dce811af969 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 07:17:26 +0900 Subject: [PATCH 3/9] feat(evidence): bind action success to verified post-condition --- .../src/action_outcome.rs | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 crates/originweave-evidence/src/action_outcome.rs diff --git a/crates/originweave-evidence/src/action_outcome.rs b/crates/originweave-evidence/src/action_outcome.rs new file mode 100644 index 00000000..8eed00ce --- /dev/null +++ b/crates/originweave-evidence/src/action_outcome.rs @@ -0,0 +1,104 @@ +use std::error::Error; +use std::fmt::{self, Display, Formatter}; + +use originweave_core::{ActionIntentDigest, ActionKind, Origin}; + +use crate::{ProvenanceRecord, VerificationResult}; + +/// A bounded observable state transition that may prove a browser action completed. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum PostConditionKind { + /// The canonical browser URL changed to the expected resulting location. + UrlChanged, + /// A governed semantic node reached the expected state after the action. + NodeStateChanged, + /// A browser dialog entered the expected visible or closed state. + DialogStateChanged, + /// A bounded network-side mutation attributable to the action was observed. + NetworkMutationObserved, +} + +/// A failure to construct successful action evidence from post-condition proof. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VerifiedActionOutcomeError { + /// The supplied provenance did not independently verify the post-condition. + PostConditionNotVerified, +} + +impl Display for VerifiedActionOutcomeError { + fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::PostConditionNotVerified => formatter.write_str( + "action success requires an independently verified post-condition", + ), + } + } +} + +impl Error for VerifiedActionOutcomeError {} + +/// Credential-safe evidence that a typed action completed its verified post-condition. +/// +/// Construction is intentionally fail-closed: a command acknowledgement, an +/// unverified observation, or rejected provenance cannot be represented by this +/// type as successful action completion. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct VerifiedActionOutcomeEvidence { + action: ActionKind, + target_origin: Origin, + intent_digest: ActionIntentDigest, + post_condition: PostConditionKind, + provenance: ProvenanceRecord, +} + +impl VerifiedActionOutcomeEvidence { + /// Create successful action evidence only from independently verified provenance. + pub fn new( + action: ActionKind, + target_origin: Origin, + intent_digest: ActionIntentDigest, + post_condition: PostConditionKind, + provenance: ProvenanceRecord, + ) -> Result { + if provenance.verification_result() != VerificationResult::Verified { + return Err(VerifiedActionOutcomeError::PostConditionNotVerified); + } + Ok(Self { + action, + target_origin, + intent_digest, + post_condition, + provenance, + }) + } + + /// Return the typed action whose completion was verified. + #[must_use] + pub const fn action(&self) -> ActionKind { + self.action + } + + /// Return the canonical origin affected by the verified action. + #[must_use] + pub const fn target_origin(&self) -> &Origin { + &self.target_origin + } + + /// Return the digest of the complete canonical action intent. + #[must_use] + pub const fn intent_digest(&self) -> &ActionIntentDigest { + &self.intent_digest + } + + /// Return the bounded post-condition that was independently verified. + #[must_use] + pub const fn post_condition(&self) -> PostConditionKind { + self.post_condition + } + + /// Return the exact provenance record that verified the post-condition. + #[must_use] + pub const fn provenance(&self) -> &ProvenanceRecord { + &self.provenance + } +} From 280d9182c8bee0afaaf5eeeca9ea08d2ff842458 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 07:18:09 +0900 Subject: [PATCH 4/9] feat(evidence): export verified action outcome contract --- crates/originweave-evidence/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/originweave-evidence/src/lib.rs b/crates/originweave-evidence/src/lib.rs index ad183e9e..184390f1 100644 --- a/crates/originweave-evidence/src/lib.rs +++ b/crates/originweave-evidence/src/lib.rs @@ -7,8 +7,12 @@ #![forbid(unsafe_code)] #![deny(missing_docs)] +mod action_outcome; mod sensitive_access; +pub use action_outcome::{ + PostConditionKind, VerifiedActionOutcomeError, VerifiedActionOutcomeEvidence, +}; pub use sensitive_access::{ MAX_SENSITIVE_FIELD_COUNT, MAX_SENSITIVE_IDENTIFIER_BYTES, SensitiveAccessClass, SensitiveAccessEvidence, SensitiveAccessEvidenceInput, SensitiveAccessOutcome, From 61d5b887fd3f0b914fa965e3b2909ca44a5a3a13 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 07:19:09 +0900 Subject: [PATCH 5/9] docs(changelog): record verified action outcome evidence --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4bd39c..8f060634 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - 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. - 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, and exact provenance, and cannot be constructed from unverified or rejected post-condition observations. - 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. @@ -73,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 98bb2efba830fb8968331b64cf16929c4005863c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 07:20:52 +0900 Subject: [PATCH 6/9] style(evidence): apply canonical action-outcome formatting --- crates/originweave-evidence/src/action_outcome.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/originweave-evidence/src/action_outcome.rs b/crates/originweave-evidence/src/action_outcome.rs index 8eed00ce..375e216e 100644 --- a/crates/originweave-evidence/src/action_outcome.rs +++ b/crates/originweave-evidence/src/action_outcome.rs @@ -28,9 +28,8 @@ pub enum VerifiedActionOutcomeError { impl Display for VerifiedActionOutcomeError { fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result { match self { - Self::PostConditionNotVerified => formatter.write_str( - "action success requires an independently verified post-condition", - ), + Self::PostConditionNotVerified => formatter + .write_str("action success requires an independently verified post-condition"), } } } From a69874a5dd85e884bcc239241245e1c79997256a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 08:08:43 +0900 Subject: [PATCH 7/9] test(evidence): require post-condition observation after dispatch --- .../tests/verified_action_outcome.rs | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/crates/originweave-evidence/tests/verified_action_outcome.rs b/crates/originweave-evidence/tests/verified_action_outcome.rs index 224725f6..4cf33ac3 100644 --- a/crates/originweave-evidence/tests/verified_action_outcome.rs +++ b/crates/originweave-evidence/tests/verified_action_outcome.rs @@ -10,6 +10,8 @@ const VALID_INTENT: &str = "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; const VALID_SOURCE_HASH: &str = "sha256:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789"; +const DISPATCHED_AT_MILLISECONDS: u64 = 1_000; +const OBSERVED_AT_MILLISECONDS: u64 = 1_025; fn intent() -> ActionIntentDigest { ActionIntentDigest::parse(VALID_INTENT).expect("valid intent digest") @@ -38,6 +40,8 @@ fn verified_post_condition_can_create_action_success_evidence() { target.clone(), intent(), PostConditionKind::NodeStateChanged, + DISPATCHED_AT_MILLISECONDS, + OBSERVED_AT_MILLISECONDS, provenance(VerificationResult::Verified), ) .expect("verified post-condition should admit success evidence"); @@ -49,6 +53,14 @@ fn verified_post_condition_can_create_action_success_evidence() { evidence.post_condition(), PostConditionKind::NodeStateChanged ); + assert_eq!( + evidence.dispatched_at_milliseconds(), + DISPATCHED_AT_MILLISECONDS + ); + assert_eq!( + evidence.observed_at_milliseconds(), + OBSERVED_AT_MILLISECONDS + ); assert_eq!( evidence.provenance().verification_result(), VerificationResult::Verified @@ -63,6 +75,8 @@ fn unverified_or_rejected_post_condition_cannot_be_recorded_as_success() { origin(), intent(), PostConditionKind::NodeStateChanged, + DISPATCHED_AT_MILLISECONDS, + OBSERVED_AT_MILLISECONDS, provenance(result), ) .expect_err("non-verified post-condition must fail closed"); @@ -76,6 +90,49 @@ fn unverified_or_rejected_post_condition_cannot_be_recorded_as_success() { } } +#[test] +fn post_condition_observation_cannot_predate_action_dispatch() { + let error = VerifiedActionOutcomeEvidence::new( + ActionKind::Submit, + origin(), + intent(), + PostConditionKind::NodeStateChanged, + 2_000, + 1_999, + provenance(VerificationResult::Verified), + ) + .expect_err("pre-dispatch observation cannot prove action success"); + + assert_eq!( + error, + VerifiedActionOutcomeError::PostConditionPredatesDispatch { + dispatched_at_milliseconds: 2_000, + observed_at_milliseconds: 1_999, + } + ); + assert_eq!( + error.to_string(), + "post-condition observation at 1999 ms predates action dispatch at 2000 ms" + ); +} + +#[test] +fn same_monotonic_tick_is_allowed_for_coarse_clock_sources() { + let evidence = VerifiedActionOutcomeEvidence::new( + ActionKind::Submit, + origin(), + intent(), + PostConditionKind::NetworkMutationObserved, + 4_000, + 4_000, + provenance(VerificationResult::Verified), + ) + .expect("coarse monotonic clocks may observe within the dispatch tick"); + + assert_eq!(evidence.dispatched_at_milliseconds(), 4_000); + assert_eq!(evidence.observed_at_milliseconds(), 4_000); +} + #[test] fn post_condition_kinds_cover_first_browser_vertical_slice_evidence() { for kind in [ @@ -89,6 +146,8 @@ fn post_condition_kinds_cover_first_browser_vertical_slice_evidence() { origin(), intent(), kind, + DISPATCHED_AT_MILLISECONDS, + OBSERVED_AT_MILLISECONDS, provenance(VerificationResult::Verified), ) .expect("supported post-condition should admit verified evidence"); From f4884b906597a4a1b3552031b6f07c2673a069f9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 08:10:57 +0900 Subject: [PATCH 8/9] feat(evidence): bind post-condition proof to dispatch ordering --- .../src/action_outcome.rs | 49 +++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/crates/originweave-evidence/src/action_outcome.rs b/crates/originweave-evidence/src/action_outcome.rs index 375e216e..ed4520f9 100644 --- a/crates/originweave-evidence/src/action_outcome.rs +++ b/crates/originweave-evidence/src/action_outcome.rs @@ -23,6 +23,13 @@ pub enum PostConditionKind { pub enum VerifiedActionOutcomeError { /// The supplied provenance did not independently verify the post-condition. PostConditionNotVerified, + /// The claimed post-condition observation happened before action dispatch. + PostConditionPredatesDispatch { + /// Monotonic timestamp recorded when the governed action was dispatched. + dispatched_at_milliseconds: u64, + /// Monotonic timestamp recorded when the post-condition was observed. + observed_at_milliseconds: u64, + }, } impl Display for VerifiedActionOutcomeError { @@ -30,6 +37,13 @@ impl Display for VerifiedActionOutcomeError { match self { Self::PostConditionNotVerified => formatter .write_str("action success requires an independently verified post-condition"), + Self::PostConditionPredatesDispatch { + dispatched_at_milliseconds, + observed_at_milliseconds, + } => write!( + formatter, + "post-condition observation at {observed_at_milliseconds} ms predates action dispatch at {dispatched_at_milliseconds} ms" + ), } } } @@ -39,34 +53,51 @@ impl Error for VerifiedActionOutcomeError {} /// Credential-safe evidence that a typed action completed its verified post-condition. /// /// Construction is intentionally fail-closed: a command acknowledgement, an -/// unverified observation, or rejected provenance cannot be represented by this -/// type as successful action completion. +/// unverified observation, rejected provenance, or an observation timestamp +/// earlier than the action dispatch cannot be represented by this type as +/// successful action completion. Equal dispatch and observation timestamps are +/// permitted because a bounded adapter may use a coarse monotonic clock. #[derive(Debug, Clone, PartialEq, Eq)] pub struct VerifiedActionOutcomeEvidence { action: ActionKind, target_origin: Origin, intent_digest: ActionIntentDigest, post_condition: PostConditionKind, + dispatched_at_milliseconds: u64, + observed_at_milliseconds: u64, provenance: ProvenanceRecord, } impl VerifiedActionOutcomeEvidence { - /// Create successful action evidence only from independently verified provenance. + /// Create successful action evidence from verified, temporally ordered provenance. + /// + /// Both timestamps must come from the same monotonic clock domain. The + /// observation may share the dispatch tick, but it may never predate it. pub fn new( action: ActionKind, target_origin: Origin, intent_digest: ActionIntentDigest, post_condition: PostConditionKind, + dispatched_at_milliseconds: u64, + observed_at_milliseconds: u64, provenance: ProvenanceRecord, ) -> Result { if provenance.verification_result() != VerificationResult::Verified { return Err(VerifiedActionOutcomeError::PostConditionNotVerified); } + if observed_at_milliseconds < dispatched_at_milliseconds { + return Err(VerifiedActionOutcomeError::PostConditionPredatesDispatch { + dispatched_at_milliseconds, + observed_at_milliseconds, + }); + } Ok(Self { action, target_origin, intent_digest, post_condition, + dispatched_at_milliseconds, + observed_at_milliseconds, provenance, }) } @@ -95,6 +126,18 @@ impl VerifiedActionOutcomeEvidence { self.post_condition } + /// Return the monotonic timestamp recorded when action dispatch began. + #[must_use] + pub const fn dispatched_at_milliseconds(&self) -> u64 { + self.dispatched_at_milliseconds + } + + /// Return the monotonic timestamp recorded when the post-condition was observed. + #[must_use] + pub const fn observed_at_milliseconds(&self) -> u64 { + self.observed_at_milliseconds + } + /// Return the exact provenance record that verified the post-condition. #[must_use] pub const fn provenance(&self) -> &ProvenanceRecord { From 2c45411ed9aa0eecca2d06c85659db9f4bb85e4d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 11 Aug 2026 08:18:38 +0900 Subject: [PATCH 9/9] docs(evidence): record dispatch-ordered outcome proof --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f060634..c1ce5509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,7 +22,7 @@ All notable changes to OriginWeave are documented in this file. The format follo - 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. - 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, and exact provenance, and cannot be constructed from unverified or rejected post-condition observations. +- 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. - 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. @@ -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 \ No newline at end of file +[Unreleased]: https://github.com/ContextualWisdomLab/OriginWeave/compare/main...HEAD