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
20 changes: 15 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [
"crates/sempai-yaml",
"crates/sempai",
"crates/weaver-cards",
"crates/weaver-docs-gate",
]
resolver = "2"

Expand All @@ -38,7 +39,7 @@ lru = "0.12"
lsp-types = "0.97"
mockall = "0.14.0"
once_cell = "1.19"
ortho_config = "0.8.0"
ortho_config = { git = "https://github.com/leynos/ortho-config.git", rev = "4339a6f3c61dc4fed86493d99ffb05230bee2a1b" }
predicates = "3.1"
proptest = "1.5"
rstest = "0.26.1"
Expand All @@ -53,6 +54,7 @@ saphyr = "0.0.6"
tempfile = "3.10"
thiserror = "2.0"
time = { version = "0.3", features = ["formatting"] }
toml = "0.9.12"
tracing = "0.1"
tree-sitter = "0.25.10"
tree-sitter-python = "0.25.0"
Expand Down
16 changes: 16 additions & 0 deletions crates/weaver-docs-gate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "weaver-docs-gate"
edition.workspace = true
version.workspace = true
rust-version.workspace = true
publish = false

[dependencies]
cap-std = { workspace = true }
camino = { workspace = true }
serde = { workspace = true }
thiserror = { workspace = true }
toml = { workspace = true }

[lints]
workspace = true
38 changes: 38 additions & 0 deletions crates/weaver-docs-gate/examples/render_boundary_matrix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Regenerate the `OrthoConfig` consumer boundary matrix.

use std::env;

use camino::Utf8Path;
use cap_std::{ambient_authority, fs::Dir};
use weaver_docs_gate::{load_manifest, render_matrix};

fn main() -> Result<(), String> {
let mut args = env::args().skip(1);
let manifest_path = args
.next()
.ok_or_else(|| "usage: render_boundary_matrix <manifest> <output>".to_owned())?;
let output_path = args
.next()
.ok_or_else(|| "usage: render_boundary_matrix <manifest> <output>".to_owned())?;

if args.next().is_some() {
return Err("usage: render_boundary_matrix <manifest> <output>".to_owned());
}

let manifest =
load_manifest(Utf8Path::new(&manifest_path)).map_err(|error| error.to_string())?;
write_output(Utf8Path::new(&output_path), render_matrix(&manifest))
}

fn write_output(path: &Utf8Path, content: String) -> Result<(), String> {
let parent = path
.parent()
.ok_or_else(|| format!("invalid output path: {path}"))?;
let file_name = path
.file_name()
.ok_or_else(|| format!("invalid output path: {path}"))?;
let dir =
Dir::open_ambient_dir(parent, ambient_authority()).map_err(|error| error.to_string())?;
dir.write(file_name, content)
.map_err(|error| error.to_string())
}
234 changes: 234 additions & 0 deletions crates/weaver-docs-gate/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
//! Tooling for the `OrthoConfig` consumer boundary matrix.

use std::io::{self, ErrorKind};

use camino::{Utf8Path, Utf8PathBuf};
use cap_std::{ambient_authority, fs::Dir};
use serde::{Deserialize, Deserializer};

mod renderer;
pub use renderer::render_matrix;

/// One boundary classification state for a Weaver roadmap task.
///
/// # Examples
/// ```
/// use weaver_docs_gate::BoundaryState;
///
/// assert_eq!(BoundaryState::Wraps.as_str(), "wraps");
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BoundaryState {
/// Weaver follows an `OrthoConfig` contract that has shipped.
Consumes,
/// Weaver uses a temporary local adapter with a removal gate.
Wraps,
/// Weaver waits for an upstream contract whose shape is undecided.
Pending,
/// Weaver deliberately keeps a different contract.
Divergent,
}

impl BoundaryState {
/// Return the manifest spelling for the state.
///
/// # Examples
/// ```
/// use weaver_docs_gate::BoundaryState;
///
/// assert_eq!(BoundaryState::Consumes.as_str(), "consumes");
/// ```
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Consumes => "consumes",
Self::Wraps => "wraps",
Self::Pending => "pending",
Self::Divergent => "divergent",
}
}
}

/// The upstream `OrthoConfig` role that a Weaver task consumes or waits for.
///
/// # Examples
/// ```
/// use weaver_docs_gate::UpstreamRole;
///
/// assert_eq!(UpstreamRole::Renderer.as_str(), "renderer");
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UpstreamRole {
/// Consumer-boundary ownership and governance.
Boundary,
/// Recursive command metadata.
Metadata,
/// Capability and provider provenance metadata.
CapabilityProvenance,
/// Canonical command vocabulary.
Vocabulary,
/// Human and machine renderer contracts.
Renderer,
/// Profile parsing, precedence, and redaction.
Profile,
/// Delivery sink contracts.
Delivery,
/// Feedback command contracts.
Feedback,
/// Execution ledger contracts.
ExecutionLedger,
}

impl UpstreamRole {
/// Return the manifest spelling for the role.
///
/// # Examples
/// ```
/// use weaver_docs_gate::UpstreamRole;
///
/// assert_eq!(UpstreamRole::Boundary.as_str(), "boundary");
/// ```
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Boundary => "boundary",
Self::Metadata => "metadata",
Self::CapabilityProvenance => "capability_provenance",
Self::Vocabulary => "vocabulary",
Self::Renderer => "renderer",
Self::Profile => "profile",
Self::Delivery => "delivery",
Self::Feedback => "feedback",
Self::ExecutionLedger => "execution_ledger",
}
}
}

/// A single upstream `OrthoConfig` task reference.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct UpstreamRef {
/// The upstream roadmap task or stable design section.
pub task: String,
/// The role that upstream reference plays for the Weaver task.
pub role: UpstreamRole,
}

/// One classified Weaver roadmap task.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct BoundaryTask {
/// Weaver roadmap task ID, such as `13.1.2`.
pub id: String,
/// One-sentence task summary.
pub gist: String,
/// Boundary classification state.
pub state: BoundaryState,
/// Upstream `OrthoConfig` task references.
pub upstream: Vec<UpstreamRef>,
/// `OrthoConfig` release tag or pinned SHA for shipped contracts.
#[serde(deserialize_with = "empty_string_as_none")]
pub shipped_in: Option<String>,
/// Replacement condition for temporary wrappers.
#[serde(deserialize_with = "empty_string_as_none")]
pub removal_gate: Option<String>,
/// ADR 007 heading slug for deliberate divergences.
#[serde(deserialize_with = "empty_string_as_none")]
pub adr_anchor: Option<String>,
/// ISO-8601 review date for pending contracts.
#[serde(deserialize_with = "empty_string_as_none")]
pub next_review_by: Option<String>,
/// ISO-8601 date when the row was last reviewed.
pub last_reviewed: String,
}

/// The complete boundary manifest.
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct BoundaryManifest {
/// Manifest schema version.
pub schema_version: u32,
/// Ordered registry of Weaver roadmap task IDs governed by the matrix.
pub managed_tasks: Vec<String>,
/// Classified task rows.
#[serde(rename = "task")]
pub tasks: Vec<BoundaryTask>,
}

/// Errors returned while loading the boundary manifest.
#[derive(Debug, thiserror::Error)]
pub enum BoundaryError {
/// The manifest path does not exist.
#[error("manifest file not found: {0}")]
NotFound(Utf8PathBuf),
/// The manifest path cannot be opened through a parent directory handle.
#[error("invalid manifest path: {0}")]
InvalidPath(Utf8PathBuf),
/// The file could not be read.
#[error("manifest file could not be read: {path}: {source}")]
Read {
/// Manifest path.
path: Utf8PathBuf,
/// Underlying I/O error.
source: Box<io::Error>,
},
/// The file is not valid TOML for the boundary schema.
#[error("invalid TOML in {path}: {source}")]
InvalidToml {
/// Manifest path.
path: Utf8PathBuf,
/// TOML parse error.
source: Box<toml::de::Error>,
},
}

/// Load the boundary manifest from disk.
///
/// # Errors
///
/// Returns [`BoundaryError`] when the manifest is missing, unreadable, or not
/// valid TOML for the boundary schema.
///
/// # Examples
/// ```no_run
/// use camino::Utf8Path;
/// use weaver_docs_gate::load_manifest;
///
/// let manifest = load_manifest(Utf8Path::new("docs/orthoconfig-consumer-boundary.toml"))?;
/// assert_eq!(manifest.schema_version, 1);
/// # Ok::<(), weaver_docs_gate::BoundaryError>(())
/// ```
pub fn load_manifest(path: &Utf8Path) -> Result<BoundaryManifest, BoundaryError> {
let parent = path.parent().unwrap_or_else(|| Utf8Path::new("."));
let file_name = path
.file_name()
.ok_or_else(|| BoundaryError::InvalidPath(path.to_path_buf()))?;
let dir = Dir::open_ambient_dir(parent, ambient_authority())
.map_err(|source| read_error(path, source))?;

let contents = dir
.read_to_string(file_name)
.map_err(|source| read_error(path, source))?;
toml::from_str(&contents).map_err(|source| BoundaryError::InvalidToml {
path: path.to_path_buf(),
source: Box::new(source),
})
}

fn read_error(path: &Utf8Path, source: io::Error) -> BoundaryError {
if source.kind() == ErrorKind::NotFound {
BoundaryError::NotFound(path.to_path_buf())
} else {
BoundaryError::Read {
path: path.to_path_buf(),
source: Box::new(source),
}
}
}

fn empty_string_as_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let value = String::deserialize(deserializer)?;
Ok((!value.is_empty()).then_some(value))
}
Loading
Loading