0.0.2-alpha.5

This commit is contained in:
2026-09-29 05:59:03 +02:00
parent 1fe39dfdeb
commit da5445354e
15 changed files with 613 additions and 26 deletions

View File

@@ -0,0 +1,19 @@
# file: crates/knowledge-app-lib/Cargo.toml
# version: 1
[package]
name = "knowledge-app-lib"
version.workspace = true
edition.workspace = true
publish.workspace = true
repository.workspace = true
[dependencies]
knowledge-git-api.workspace = true
knowledge-sync-api.workspace = true
[dev-dependencies]
knowledge-domain-lib.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,14 @@
// file: crates/knowledge-app-lib/src/lib.rs
// version: 1
//! Shared application use cases for SaseDev Knowledge.
//!
//! This crate composes provider-neutral application contracts. It owns no Git process, scheduler,
//! database, HTTP runtime or forge integration.
mod repository_sync;
/// Result of reconciling one repository-backed content source.
pub use self::repository_sync::RepositorySyncOutcome;
/// Reconcile one source through the provider-neutral Git repository contract.
pub use self::repository_sync::synchronize_repository;

View File

@@ -0,0 +1,89 @@
// file: crates/knowledge-app-lib/src/repository_sync.rs
// version: 1
/// Result of reconciling one repository-backed content source.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum RepositorySyncOutcome {
/// The observed revision matches the revision already known by the caller.
Unchanged {
/// Authoritative revision observed after refresh and resolution.
revision: knowledge_git_api::GitRevision,
},
/// The observed revision differs and a read snapshot has been prepared.
Changed {
/// Authoritative revision observed after refresh and resolution.
revision: knowledge_git_api::GitRevision,
/// Read-only snapshot prepared for the new authoritative revision.
snapshot: knowledge_git_api::GitSnapshot,
},
}
impl crate::RepositorySyncOutcome {
/// Return the authoritative revision observed by reconciliation.
pub fn revision(&self) -> &knowledge_git_api::GitRevision {
match self {
crate::RepositorySyncOutcome::Unchanged { revision } => return revision,
crate::RepositorySyncOutcome::Changed { revision, snapshot: _ } => return revision,
}
}
/// Return the prepared snapshot when reconciliation detected a change.
pub fn snapshot(&self) -> Option<&knowledge_git_api::GitSnapshot> {
match self {
crate::RepositorySyncOutcome::Unchanged { revision: _ } => return None,
crate::RepositorySyncOutcome::Changed { revision: _, snapshot } => return Some(snapshot),
}
}
/// Return whether reconciliation detected a new authoritative revision.
pub fn is_changed(&self) -> bool {
match self {
crate::RepositorySyncOutcome::Unchanged { revision: _ } => return false,
crate::RepositorySyncOutcome::Changed { revision: _, snapshot: _ } => return true,
}
}
}
/// Reconcile one source through the provider-neutral Git repository contract.
///
/// The signal cause does not change the reconciliation path: explicit, periodic and external
/// signals all request the same authoritative refresh/resolve/compare operation. The caller owns
/// persistence of the previous revision and any scheduling policy.
pub fn synchronize_repository<R>(
repository: &mut R,
signal: &knowledge_sync_api::RepositorySyncSignal,
reference: &knowledge_git_api::GitReference,
previous_revision: Option<&knowledge_git_api::GitRevision>,
) -> Result<crate::RepositorySyncOutcome, R::Error>
where
R: knowledge_git_api::GitRepository,
{
let refresh_result = knowledge_git_api::GitRepository::refresh(repository, signal.source_id());
match refresh_result {
Ok(()) => (),
Err(error) => return Err(error),
}
let revision_result = knowledge_git_api::GitRepository::resolve_revision(repository, signal.source_id(), reference);
let revision = match revision_result {
Ok(value) => value,
Err(error) => return Err(error),
};
match previous_revision {
Some(previous) => {
if previous == &revision {
return Ok(crate::RepositorySyncOutcome::Unchanged { revision });
}
},
None => (),
}
let snapshot_result = knowledge_git_api::GitRepository::prepare_snapshot(repository, signal.source_id(), &revision);
let snapshot = match snapshot_result {
Ok(value) => value,
Err(error) => return Err(error),
};
return Ok(crate::RepositorySyncOutcome::Changed { revision, snapshot });
}
#[cfg(test)]
#[path = "../unit_tests/repository_sync.rs"]
mod tests;

View File

@@ -0,0 +1,121 @@
// file: crates/knowledge-app-lib/unit_tests/repository_sync.rs
// version: 1
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct FakeRepositoryError;
struct FakeRepository {
observed_revision: knowledge_git_api::GitRevision,
reject_snapshot: bool,
}
impl FakeRepository {
fn new(observed_revision: &str, reject_snapshot: bool) -> FakeRepository {
return FakeRepository { observed_revision: knowledge_git_api::GitRevision::new(observed_revision), reject_snapshot };
}
}
impl knowledge_git_api::GitRepository for FakeRepository {
type Error = FakeRepositoryError;
fn refresh(&mut self, _source_id: &knowledge_domain_lib::SourceId) -> Result<(), Self::Error> {
return Ok(());
}
fn resolve_revision(
&self,
_source_id: &knowledge_domain_lib::SourceId,
_reference: &knowledge_git_api::GitReference,
) -> Result<knowledge_git_api::GitRevision, Self::Error> {
return Ok(self.observed_revision.clone());
}
fn prepare_snapshot(
&self,
_source_id: &knowledge_domain_lib::SourceId,
revision: &knowledge_git_api::GitRevision,
) -> Result<knowledge_git_api::GitSnapshot, Self::Error> {
if self.reject_snapshot {
return Err(FakeRepositoryError);
}
return Ok(knowledge_git_api::GitSnapshot::new(revision.clone(), std::path::PathBuf::from("/controlled/snapshot")));
}
}
fn source_id() -> Option<knowledge_domain_lib::SourceId> {
let result = knowledge_domain_lib::SourceId::new("snippets");
match result {
Ok(value) => return Some(value),
Err(_) => return None,
}
}
fn signal(cause: knowledge_sync_api::RepositorySyncCause) -> Option<knowledge_sync_api::RepositorySyncSignal> {
let id = match source_id() {
Some(value) => value,
None => return None,
};
return Some(knowledge_sync_api::RepositorySyncSignal::new(id, cause));
}
#[test]
fn changed_revision_prepares_snapshot_and_returns_observed_revision() {
let mut repository = FakeRepository::new("bbbb", false);
let request = match signal(knowledge_sync_api::RepositorySyncCause::Explicit) {
Some(value) => value,
None => return,
};
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
let previous = knowledge_git_api::GitRevision::new("aaaa");
let result = crate::synchronize_repository(&mut repository, &request, &reference, Some(&previous));
assert!(result.is_ok());
let outcome = match result {
Ok(value) => value,
Err(_) => return,
};
assert!(outcome.is_changed());
assert_eq!(outcome.revision().as_str(), "bbbb");
assert!(outcome.snapshot().is_some());
return;
}
#[test]
fn unchanged_revision_does_not_prepare_snapshot() {
let mut repository = FakeRepository::new("bbbb", true);
let request = match signal(knowledge_sync_api::RepositorySyncCause::Explicit) {
Some(value) => value,
None => return,
};
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
let previous = knowledge_git_api::GitRevision::new("bbbb");
let result = crate::synchronize_repository(&mut repository, &request, &reference, Some(&previous));
assert!(result.is_ok());
let outcome = match result {
Ok(value) => value,
Err(_) => return,
};
assert!(!outcome.is_changed());
assert_eq!(outcome.revision().as_str(), "bbbb");
assert!(outcome.snapshot().is_none());
return;
}
#[test]
fn periodic_signal_uses_the_same_reconciliation_path() {
let mut repository = FakeRepository::new("cccc", false);
let request = match signal(knowledge_sync_api::RepositorySyncCause::Periodic) {
Some(value) => value,
None => return,
};
let reference = knowledge_git_api::GitReference::new("refs/heads/main");
let result = crate::synchronize_repository(&mut repository, &request, &reference, None);
assert!(result.is_ok());
let outcome = match result {
Ok(value) => value,
Err(_) => return,
};
assert!(outcome.is_changed());
assert_eq!(outcome.revision().as_str(), "cccc");
assert!(outcome.snapshot().is_some());
return;
}