0.0.3-alpha.4

This commit is contained in:
2026-09-29 13:57:45 +02:00
parent aca00e6595
commit a87f3ba4ab
12 changed files with 857 additions and 23 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/knowledge-git-lib/src/error.rs
// version: 4
// version: 5
//! Recoverable errors for controlled Git process execution.
@@ -30,6 +30,10 @@ pub enum GitOperation {
PreserveCandidate,
/// Remove one backend-owned candidate worktree.
RemoveWriteWorktree,
/// Observe the current state of one remote branch.
InspectRemote,
/// Push one candidate revision with an explicit compare-and-exchange guard.
PushCandidate,
}
impl std::fmt::Display for crate::GitOperation {
@@ -47,6 +51,8 @@ impl std::fmt::Display for crate::GitOperation {
crate::GitOperation::CommitCandidate => return formatter.write_str("commit candidate"),
crate::GitOperation::PreserveCandidate => return formatter.write_str("preserve candidate"),
crate::GitOperation::RemoveWriteWorktree => return formatter.write_str("remove write worktree"),
crate::GitOperation::InspectRemote => return formatter.write_str("inspect remote branch"),
crate::GitOperation::PushCandidate => return formatter.write_str("push guarded candidate"),
}
}
}

View File

@@ -1,11 +1,11 @@
// file: crates/knowledge-git-lib/src/lib.rs
// version: 4
// version: 5
//! Controlled Git process backend for Knowledge content sources.
//!
//! This crate owns physical Git source configuration, generated-state layout, provider-neutral read
//! materialization, isolated candidate construction and process execution. It deliberately contains no
//! forge API, persistence, scheduler or application orchestration.
//! materialization, isolated candidate construction, guarded remote branch updates and process execution.
//! It deliberately contains no forge API, persistence, scheduler or application orchestration.
mod backend;
mod error;

View File

@@ -1,7 +1,7 @@
// file: crates/knowledge-git-lib/src/write.rs
// version: 1
// version: 2
//! Isolated candidate-commit creation for controlled Git sources.
//! Isolated candidate-commit creation and guarded remote publication for controlled Git sources.
impl crate::GitBackend {
/// Create one detached candidate commit from an explicit resolved base revision.
@@ -223,6 +223,189 @@ impl crate::GitBackend {
Err(_) => return Err(crate::GitBackendError::WriteCleanupFailed),
}
}
/// Attempt one guarded remote branch update for a retained candidate revision.
///
/// The remote is first observed for an early conflict result, then Git's server-side
/// `--force-with-lease=<ref>:<expected>` guard performs the authoritative compare-and-exchange.
/// A failed guarded push is observed again so a concurrent update is returned as a recoverable
/// [`knowledge_git_api::GitPushOutcome::Conflict`] instead of being collapsed into a process error.
pub fn push_candidate(
&mut self,
source_id: &knowledge_domain_lib::SourceId,
request: &knowledge_git_api::GitPushRequest,
) -> Result<knowledge_git_api::GitPushOutcome, crate::GitBackendError> {
if !crate::is_safe_revision(request.candidate_revision().as_str()) {
return Err(crate::GitBackendError::InvalidRevision);
}
let source_result = self.configured_source(source_id);
let source = match source_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let mirror_result = self.ensure_mirror(source);
let mirror = match mirror_result {
Ok(value) => value,
Err(error) => return Err(error),
};
let candidate_result = self.validate_retained_candidate(mirror.repository_path(), request.candidate_revision());
match candidate_result {
Ok(()) => (),
Err(error) => return Err(error),
}
let observed_result = self.observe_remote_state(mirror.repository_path(), request.target_branch());
let observed = match observed_result {
Ok(value) => value,
Err(error) => return Err(error),
};
if &observed != request.expected_remote() {
return Ok(knowledge_git_api::GitPushOutcome::Conflict { actual: observed });
}
let lease = match request.expected_remote() {
knowledge_git_api::GitRemoteState::Absent => format!("--force-with-lease={}:", request.target_branch().as_str()),
knowledge_git_api::GitRemoteState::Revision(revision) => {
if !crate::is_safe_revision(revision.as_str()) {
return Err(crate::GitBackendError::InvalidRevision);
}
format!("--force-with-lease={}:{}", request.target_branch().as_str(), revision.as_str())
},
};
let refspec = format!("{}:{}", request.candidate_revision().as_str(), request.target_branch().as_str());
let push_result = self.run_controlled_git(
crate::GitOperation::PushCandidate,
vec![
std::ffi::OsString::from("--git-dir"),
mirror.repository_path().as_os_str().to_os_string(),
std::ffi::OsString::from("-c"),
std::ffi::OsString::from("remote.origin.mirror=false"),
std::ffi::OsString::from("push"),
std::ffi::OsString::from("--porcelain"),
std::ffi::OsString::from("--no-verify"),
std::ffi::OsString::from(lease),
std::ffi::OsString::from("origin"),
std::ffi::OsString::from(refspec),
],
Vec::new(),
);
match push_result {
Ok(_) => return Ok(knowledge_git_api::GitPushOutcome::Updated(request.candidate_revision().clone())),
Err(push_error) => {
let after_result = self.observe_remote_state(mirror.repository_path(), request.target_branch());
let after = match after_result {
Ok(value) => value,
Err(_) => return Err(push_error),
};
if &after != request.expected_remote() {
if after == knowledge_git_api::GitRemoteState::Revision(request.candidate_revision().clone()) {
return Ok(knowledge_git_api::GitPushOutcome::Updated(request.candidate_revision().clone()));
}
return Ok(knowledge_git_api::GitPushOutcome::Conflict { actual: after });
}
return Err(push_error);
},
}
}
fn validate_retained_candidate(&self, repository_path: &std::path::Path, candidate: &knowledge_git_api::GitRevision) -> Result<(), crate::GitBackendError> {
let candidate_reference = format!("refs/knowledge/candidates/{}^{{commit}}", candidate.as_str());
let output_result = self.run_controlled_git(
crate::GitOperation::InspectCandidate,
vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("rev-parse"),
std::ffi::OsString::from("--verify"),
std::ffi::OsString::from("--end-of-options"),
std::ffi::OsString::from(candidate_reference),
],
Vec::new(),
);
let output = match output_result {
Ok(value) => value,
Err(_) => return Err(crate::GitBackendError::InvalidCandidate),
};
let text_result = std::str::from_utf8(&output);
let text = match text_result {
Ok(value) => value.trim(),
Err(_) => return Err(crate::GitBackendError::InvalidCandidate),
};
if text != candidate.as_str() {
return Err(crate::GitBackendError::InvalidCandidate);
}
return Ok(());
}
fn observe_remote_state(
&self,
repository_path: &std::path::Path,
branch: &knowledge_git_api::GitBranch,
) -> Result<knowledge_git_api::GitRemoteState, crate::GitBackendError> {
let output_result = self.run_controlled_git(
crate::GitOperation::InspectRemote,
vec![
std::ffi::OsString::from("--git-dir"),
repository_path.as_os_str().to_os_string(),
std::ffi::OsString::from("ls-remote"),
std::ffi::OsString::from("--refs"),
std::ffi::OsString::from("origin"),
std::ffi::OsString::from(branch.as_str()),
],
Vec::new(),
);
let output = match output_result {
Ok(value) => value,
Err(error) => return Err(error),
};
if output.is_empty() {
return Ok(knowledge_git_api::GitRemoteState::Absent);
}
let text_result = std::str::from_utf8(&output);
let text = match text_result {
Ok(value) => value.trim(),
Err(_) => return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote }),
};
let mut lines = text.lines();
let line = match lines.next() {
Some(value) => value,
None => return Ok(knowledge_git_api::GitRemoteState::Absent),
};
if lines.next().is_some() {
return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote });
}
let mut fields = line.split('\t');
let revision = match fields.next() {
Some(value) => value,
None => return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote }),
};
let remote_reference = match fields.next() {
Some(value) => value,
None => return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote }),
};
if fields.next().is_some() || remote_reference != branch.as_str() || !crate::is_safe_revision(revision) {
return Err(crate::GitBackendError::UnexpectedGitOutput { operation: crate::GitOperation::InspectRemote });
}
return Ok(knowledge_git_api::GitRemoteState::Revision(knowledge_git_api::GitRevision::new(revision)));
}
}
impl knowledge_git_api::GitRepositoryWriter for crate::GitBackend {
type Error = crate::GitBackendError;
fn create_candidate(
&mut self,
source_id: &knowledge_domain_lib::SourceId,
request: &knowledge_git_api::GitWriteRequest,
) -> Result<knowledge_git_api::GitRevision, Self::Error> {
return crate::GitBackend::create_candidate(self, source_id, request);
}
fn push_candidate(
&mut self,
source_id: &knowledge_domain_lib::SourceId,
request: &knowledge_git_api::GitPushRequest,
) -> Result<knowledge_git_api::GitPushOutcome, Self::Error> {
return crate::GitBackend::push_candidate(self, source_id, request);
}
}
fn apply_mutation(worktree_path: &std::path::Path, mutation: &knowledge_git_api::GitFileMutation) -> Result<(), crate::GitBackendError> {