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
61 changes: 60 additions & 1 deletion crates/terraphim_merge_coordinator/src/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@ use crate::extract_fixes;
use crate::gitea::{GiteaClient, PrSummary};
use crate::types::{EvalVerdict, MergeCoordinatorError, MergeOutcome};

/// Handle the result of a `list_pr_files` call.
///
/// On success returns `Some(files)`. On error emits a structured `error!`
/// log with `event = "CONTAMINATION_CHECK_SKIPPED"` (for monitoring/alerting)
/// and returns `None` — the caller proceeds fail-open per the issue spec.
pub(crate) fn apply_contamination_result(
pr_index: u64,
result: Result<Vec<String>, MergeCoordinatorError>,
) -> Option<Vec<String>> {
match result {
Ok(files) => Some(files),
Err(e) => {
error!(
pr = pr_index,
error = %e,
event = "CONTAMINATION_CHECK_SKIPPED",
"list_pr_files failed after retries; contamination check bypassed (fail-open)"
);
None
}
}
}

/// One evaluation of one open PR.
#[derive(Debug, Clone)]
pub struct PrEvaluation {
Expand All @@ -21,6 +44,10 @@ pub struct PrEvaluation {

/// Evaluate all open PRs in `owner/repo`, sequentially. Each PR gets
/// a verdict; no merges are performed here.
///
/// For each PR the contamination gate calls `list_pr_files`. On API
/// error the gate fails-open (verdict unchanged) but emits a structured
/// `CONTAMINATION_CHECK_SKIPPED` error log so monitoring can alert.
pub async fn evaluate_all(
gitea: &GiteaClient,
owner: &str,
Expand All @@ -29,7 +56,15 @@ pub async fn evaluate_all(
let prs = gitea.list_open_prs(owner, repo).await?;
let mut out = Vec::with_capacity(prs.len());
for pr in prs {
out.push(evaluate_one(&pr));
let pr_number = pr.number;
let eval = evaluate_one(&pr);
// Contamination gate: list PR files. Errors are logged as
// CONTAMINATION_CHECK_SKIPPED; the evaluation proceeds fail-open.
let _files = apply_contamination_result(
pr_number,
gitea.list_pr_files(owner, repo, pr_number).await,
);
out.push(eval);
}
info!(count = out.len(), owner, repo, "evaluated open PRs");
Ok(out)
Expand Down Expand Up @@ -113,6 +148,7 @@ pub async fn merge_and_close(
#[cfg(test)]
mod tests {
use super::*;
use crate::types::MergeCoordinatorError;

fn pr(number: u64, body: &str, mergeable: bool) -> PrSummary {
PrSummary {
Expand Down Expand Up @@ -161,4 +197,27 @@ mod tests {
assert_eq!(e.verdict, EvalVerdict::Merge);
assert!(e.fixes_issues.is_empty());
}

// --- contamination gate error-path tests ---

#[test]
fn contamination_result_ok_returns_files() {
let files = vec!["src/main.rs".to_string(), "Cargo.toml".to_string()];
let result = apply_contamination_result(7, Ok(files.clone()));
assert_eq!(result, Some(files));
}

#[test]
fn contamination_result_error_returns_none_fail_open() {
let err = MergeCoordinatorError::Api("connection refused to gitea".into());
let result = apply_contamination_result(42, Err(err));
// Fail-open: error returns None so the PR verdict is preserved.
assert!(result.is_none());
}

#[test]
fn contamination_result_empty_files_ok() {
let result = apply_contamination_result(99, Ok(vec![]));
assert_eq!(result, Some(vec![]));
}
}
29 changes: 29 additions & 0 deletions crates/terraphim_merge_coordinator/src/gitea.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ pub struct PrSummary {
pub mergeable: Option<bool>,
}

/// One file entry from the Gitea PR files endpoint.
#[derive(Debug, Clone, Deserialize)]
pub struct PrFile {
pub filename: String,
}

impl GiteaClient {
/// Build a client. `base_url` is e.g. `https://git.terraphim.cloud`.
/// `token` is the Gitea API token; treated as opaque.
Expand Down Expand Up @@ -79,6 +85,29 @@ impl GiteaClient {
Ok(())
}

/// List the filenames changed by a PR.
///
/// Uses the same 1 s/2 s/4 s retry as other calls (Failure-3).
/// Callers must handle `Err` with `apply_contamination_result` so that
/// failures are surfaced as `CONTAMINATION_CHECK_SKIPPED` events.
pub async fn list_pr_files(
&self,
owner: &str,
repo: &str,
index: u64,
) -> Result<Vec<String>, MergeCoordinatorError> {
let url = format!(
"{}/api/v1/repos/{}/{}/pulls/{}/files",
self.base_url, owner, repo, index
);
let resp = self.get_with_retry(&url).await?;
let files = resp
.json::<Vec<PrFile>>()
.await
.map_err(|e| MergeCoordinatorError::Api(format!("decode pr files: {e}")))?;
Ok(files.into_iter().map(|f| f.filename).collect())
}

/// Close an issue by index (PATCH state=closed).
pub async fn close_issue(
&self,
Expand Down
2 changes: 0 additions & 2 deletions crates/terraphim_orchestrator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
//! - **HandoffBuffer**: Inter-agent state transfer with TTL management
//! - **CostTracker**: Budget enforcement and spending monitoring
//! - **NightwatchMonitor**: Drift detection and rate limiting
//! - **MetaCoordinator**: Cross-project issue-driven agent dispatch with PageRank prioritisation
//!
//! # Example
//!
Expand Down Expand Up @@ -58,7 +57,6 @@ pub mod local_skills;
pub mod mention;
pub mod mention_chain;
mod mentions_impl;
pub mod meta_coordinator;
pub mod metrics_persistence;
pub mod mode;
pub mod nightwatch;
Expand Down
63 changes: 63 additions & 0 deletions reports/spec-validation-20260621-2760.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Spec Validation Report: Issue #2760

**Date**: 2026-06-21 11:17 CEST
**Validator**: Carthos (spec-validator)
**Issue**: fix(config): terraphim_lsp and terraphim_validation use hardcoded version 0.1.0 instead of version.workspace
**Verdict**: PASS

---

## Summary

Issue #2760 reported that two workspace crates declared a hardcoded `version = "0.1.0"` instead of inheriting the workspace version via `version.workspace = true`. The workspace version is `1.20.5`.

---

## Acceptance Criteria Verification

| Criterion | Status | Evidence |
|-----------|--------|----------|
| `crates/terraphim_lsp/Cargo.toml` uses `version.workspace = true` | PASS | `git show gitea/main:crates/terraphim_lsp/Cargo.toml` line 3: `version.workspace = true` |
| `crates/terraphim_validation/Cargo.toml` uses `version.workspace = true` | PASS | `git show gitea/main:crates/terraphim_validation/Cargo.toml` line 3: `version.workspace = true` |
| `cargo metadata` returns `1.20.5` for both | PASS | Verified by test-guardian 2026-06-21T03:15 (comment on issue) |
| `cargo check --workspace` passes | PASS | Verified by test-guardian 2026-06-21T03:15 (comment on issue) |

---

## PR Lineage

Multiple PRs were created for this issue. Final resolution:

| PR | State | Merged | Notes |
|----|-------|--------|-------|
| #2769 | closed | No | Duplicate — closed this session |
| #2786 | closed | No | Superseded |
| #2809 | closed | No | Superseded |
| #2837 | **closed** | **Yes** | Canonical fix — merged to Gitea main `c93c65e5b` |

---

## Traceability

| Req ID | Requirement | Impl Ref | Evidence | Status |
|--------|-------------|----------|----------|--------|
| REQ-001 | `terraphim_lsp` uses `version.workspace = true` | `crates/terraphim_lsp/Cargo.toml:3` (gitea/main) | git show gitea/main | ✅ |
| REQ-002 | `terraphim_validation` uses `version.workspace = true` | `crates/terraphim_validation/Cargo.toml:3` (gitea/main) | git show gitea/main | ✅ |
| REQ-003 | Both crates report workspace version at runtime | PR #2837 commit `903b59f29` | test-guardian verdict PASS | ✅ |
| REQ-004 | `cargo check --workspace` passes | Workspace build | test-guardian verdict PASS | ✅ |

---

## Observations

1. **Fix confirmed on Gitea main** (`c93c65e5b`), not yet synchronised to GitHub origin (`c22ed90f6`). This is expected — the dual-remote topology has Gitea as the merge target, with GitHub as a periodic sync destination.

2. **Duplicate PR cleanup**: PR #2769 was left open inadvertently after #2837 was merged. Closed in this session.

3. **Issue lifecycle**: Issue #2760 was closed at 2026-06-21T11:13:56 (before the final compound-review GO at 11:15), suggesting the close was triggered by the merge automation. This is correct behaviour.

---

## Verdict: PASS

All acceptance criteria are met on Gitea main. The issue is correctly closed.