Skip to content
Merged
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
84 changes: 13 additions & 71 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,6 @@ thiserror = "2"
tracing = { version = "0.1", default-features = false, features = ["std"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "time"] }
rusqlite = { version = "0.40", features = ["bundled"] }
sha2 = "0.10"
sha2 = "0.11"
chrono = { version = "0.4", default-features = false, features = ["clock", "serde", "std"] }
uuid = { version = "1", features = ["v4", "serde"] }
5 changes: 3 additions & 2 deletions crates/tinyflows/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ tokio = { version = "1", features = ["macros", "rt-multi-thread", "sync", "time"
reqwest = { version = "0.13", default-features = false, features = ["json"], optional = true }
# `caps::host` only: hashing an author-supplied state key (and namespace) into
# one safe path component, and staging a script in a temporary directory.
sha2 = { version = "0.10", optional = true }
sha2 = { version = "0.11", optional = true }
hex = "0.4"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

priority medium critique confident

Gate hex behind the host and store features

hex is used only by caps::host and store, but this declaration makes it a dependency of every default tinyflows build. That bypasses the repository rule that host/store functionality and their dependencies remain behind default-off features, and forces the engine to resolve and compile an otherwise unused dependency. Make hex optional and add dep:hex to each feature that uses it (host-caps and store).

[RULE] feature-gating ·

tempfile = { version = "3", optional = true }
# `store` only: the advisory file lock that keeps two processes from deciding
# the same proposal at once.
Expand Down Expand Up @@ -81,7 +82,7 @@ proptest = "1"
# `host-caps` feature is on (the same rule `caps::mock` follows), so what that
# feature would switch on has to be present for a test build too. An optional
# dependency is not activated by `cfg(test)`, hence these.
sha2 = "0.10"
sha2 = "0.11"
tempfile = "3"
fs2 = "0.4"
reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyflows/src/caps/host/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ impl FileStateStore {
fn digest(value: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(value.as_bytes());
format!("{:x}", hasher.finalize())
hex::encode(hasher.finalize())
}

#[async_trait]
Expand Down
5 changes: 1 addition & 4 deletions crates/tinyflows/src/store/file/dirs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,7 @@ pub fn workflow_dirs(home: &Path, cwd: &Path, project_dir: &str) -> Vec<PathBuf>
/// State shared by stores writing the same catalog, beneath the caller's root.
pub(crate) fn definition_state_dir(state_root: &Path, dirs: &[PathBuf]) -> PathBuf {
let write_dir = catalog_identity(dirs);
let scope = format!(
"{:x}",
Sha256::digest(write_dir.as_os_str().as_encoded_bytes())
);
let scope = hex::encode(Sha256::digest(write_dir.as_os_str().as_encoded_bytes()));
state_root.join("definitions").join(scope)
}

Expand Down
4 changes: 2 additions & 2 deletions crates/tinyflows/src/store/file/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ fn scoped_state_dir(state_dir: &Path, workspace: &Path) -> PathBuf {
/// drift.
pub fn workspace_scope(workspace: &Path) -> String {
let identity = std::fs::canonicalize(workspace).unwrap_or_else(|_| absolute_path(workspace));
let digest = Sha256::digest(identity.to_string_lossy().as_bytes());
format!("{digest:x}")[..16].to_string()
let digest = hex::encode(Sha256::digest(identity.to_string_lossy().as_bytes()));
digest[..16].to_string()
}

/// A file-backed proposal decision claim released when dropped.
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyflows/src/store/types/proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ impl WorkflowProposal {
pub fn fingerprint(graph: &crate::model::WorkflowGraph) -> String {
use sha2::{Digest, Sha256};
match serde_json::to_vec(graph) {
Ok(canonical) => format!("{:x}", Sha256::digest(&canonical)),
Ok(canonical) => hex::encode(Sha256::digest(&canonical)),
// A graph that fails to serialize (a non-finite `Position`, for
// instance) must not fingerprint the same as every other graph that
// also fails to serialize. Hashing empty bytes would do exactly that,
Expand Down
2 changes: 1 addition & 1 deletion crates/tinyflows/src/store/types/workflow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ pub fn record_fingerprint(record: &WorkflowRecord) -> String {
let mut persisted = record.clone();
persisted.source_path = None;
match serde_json::to_vec(&persisted) {
Ok(canonical) => format!("{:x}", Sha256::digest(&canonical)),
Ok(canonical) => hex::encode(Sha256::digest(&canonical)),
// Same reasoning as `proposal::fingerprint`: hashing empty bytes on a
// serialization failure would let a compare-and-swap write accept a
// stale record whenever both the expected and current record happen
Expand Down
Loading