Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
61909e8
feat(contract): require structured review results (#119)
CompleteDotTech Jul 6, 2026
fdc255f
feat(contract): record supporting review files (#119)
CompleteDotTech Jul 6, 2026
44d2663
chore(hosted): track CompleteTech dogfood adapter (#119)
CompleteDotTech Jul 6, 2026
3cd400a
feat(hosted): add bounded review fix loop (#119)
CompleteDotTech Jul 6, 2026
669fca6
fix(hosted): address review gating and evidence gaps (#119)
CompleteDotTech Jul 6, 2026
a6a794d
fix(hosted): apply review hardening (#119)
CompleteDotTech Jul 6, 2026
7cfc1be
fix(hosted): align review context and routing contract (#119)
CompleteDotTech Jul 6, 2026
4e19b93
fix(hosted): enforce result contract before review loops (#119)
CompleteDotTech Jul 6, 2026
9091dc6
fix(hosted): tighten review result validation (#119)
CompleteDotTech Jul 6, 2026
cacece4
fix(worker): enforce v2 review contract invariants (#119)
CompleteDotTech Jul 6, 2026
6bdfb7b
fix(contract): validate result exit reasons (#119)
CompleteDotTech Jul 6, 2026
2cc54f7
fix(contract): close result envelope validation (#119)
CompleteDotTech Jul 6, 2026
cec7a28
fix(contract): align session brief validation (#119)
CompleteDotTech Jul 6, 2026
e5b59c8
fix(hosted): harden review routing evidence (#119)
CompleteDotTech Jul 6, 2026
00c3793
ci: run hosted adapter python tests (#119)
CompleteDotTech Jul 6, 2026
2584b34
fix(hosted): reject team-style bot mentions (#119)
CompleteDotTech Jul 6, 2026
2617a2c
Merge branch 'main' into codex/review-fix-loop
BunsDev Jul 6, 2026
bf9c035
Potential fix for pull request finding
BunsDev Jul 6, 2026
089d347
fix(worker): align v2 review result contract
CompleteDotTech Jul 6, 2026
5d22cce
fix(adapter): handle missing webhook secret
CompleteDotTech Jul 6, 2026
d03b5a2
Merge remote-tracking branch 'origin/main' into codex/review-fix-loop
CompleteDotTech Jul 6, 2026
73bb736
Merge branch 'main' into codex/review-fix-loop
BunsDev Jul 6, 2026
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ keys/
*.pem
.env
.DS_Store
deploy/coven-github/.coven-github-private-key.pem
deploy/coven-github/coven-github-policy.json
deploy/coven-github/coven-github-state/
83 changes: 82 additions & 1 deletion crates/github/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub const DEFAULT_API_BASE_URL: &str = "https://api.github.com";

/// Major version of the coven-code headless execution contract this adapter
/// speaks. See `docs/headless-contract.md`. Bump only on breaking changes.
pub const HEADLESS_CONTRACT_VERSION: &str = "1";
pub const HEADLESS_CONTRACT_VERSION: &str = "2";

fn default_contract_version() -> String {
HEADLESS_CONTRACT_VERSION.to_string()
Expand Down Expand Up @@ -187,6 +187,7 @@ pub struct SessionResult {
pub files_changed: Vec<String>,
pub summary: String,
pub pr_body: String,
pub review: ReviewResult,
Comment thread
CompleteDotTech marked this conversation as resolved.
pub exit_reason: Option<ExitReason>,
}

Expand All @@ -205,6 +206,86 @@ pub struct CommitInfo {
pub message: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ReviewResult {
pub mode: ReviewMode,
pub evidence_status: ReviewEvidenceStatus,
pub reviewed_files: Vec<String>,
pub supporting_files: Vec<String>,
pub findings: Vec<ReviewFinding>,
pub tests_run: Vec<ReviewTestRun>,
pub no_findings_reason: Option<String>,
pub limitations: Vec<String>,
}

impl ReviewResult {
pub fn none() -> Self {
Self {
mode: ReviewMode::None,
evidence_status: ReviewEvidenceStatus::NotApplicable,
reviewed_files: Vec::new(),
supporting_files: Vec::new(),
findings: Vec::new(),
tests_run: Vec::new(),
no_findings_reason: None,
limitations: Vec::new(),
}
}
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ReviewMode {
None,
PullRequest,
ReviewComment,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ReviewEvidenceStatus {
NotApplicable,
Complete,
Partial,
Missing,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ReviewFinding {
pub severity: ReviewSeverity,
pub file: String,
pub line: Option<u64>,
pub title: String,
pub body: String,
pub recommendation: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ReviewSeverity {
Info,
Low,
Medium,
High,
Critical,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct ReviewTestRun {
pub command: String,
pub status: ReviewTestStatus,
pub output_summary: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ReviewTestStatus {
Passed,
Failed,
NotRun,
Unknown,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ExitReason {
Expand Down
1 change: 1 addition & 0 deletions crates/github/src/tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ mod tests {
files_changed: vec![],
summary: "Done".to_string(),
pr_body: "Body".to_string(),
review: crate::ReviewResult::none(),
exit_reason: None,
},
Some(9),
Expand Down
47 changes: 42 additions & 5 deletions crates/worker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ fn disposition(result: &SessionResult) -> Disposition {
enum Attempt {
/// The runtime exited 0/1/3 and wrote a parseable `result.json`. Terminal:
/// the adapter acts on `status`/`exit_reason` and MUST NOT retry.
Completed(SessionResult),
Completed(Box<SessionResult>),
/// Exit 2, timeout, kill-by-signal, an unexpected exit code, or a spawn/read
/// failure. Retry-safe per the contract.
RetrySafe(anyhow::Error),
Expand Down Expand Up @@ -430,7 +430,7 @@ async fn run_session_with_backoff(
let mut attempts = 0u32;
loop {
match run_coven_code(config, brief_path, result_path, git_token).await {
Attempt::Completed(result) => return Ok(result),
Attempt::Completed(result) => return Ok(*result),
Attempt::RetrySafe(e) if attempts < max_retries => {
attempts += 1;
warn!("coven-code attempt {attempts} hit a retry-safe failure ({e:#}), retrying…");
Expand Down Expand Up @@ -488,7 +488,7 @@ async fn run_coven_code(
// exit codes; if it isn't, the runtime misbehaved — fall back to a
// retry-safe failure rather than silently losing the task.
Some(code @ (0 | 1 | 3)) => match read_result(result_path).await {
Ok(result) => Attempt::Completed(result),
Ok(result) => Attempt::Completed(Box::new(result)),
Err(e) => Attempt::RetrySafe(anyhow::anyhow!(
"coven-code exited {code} but result.json was unusable: {e}"
)),
Expand All @@ -506,6 +506,13 @@ async fn read_result(result_path: &Path) -> Result<SessionResult> {
.await
.map_err(|_| anyhow::anyhow!("result.json not written by coven-code"))?;
let result: SessionResult = serde_json::from_slice(&bytes)?;
if result.contract_version != coven_github_api::HEADLESS_CONTRACT_VERSION {
anyhow::bail!(
"unsupported result contract_version {}; expected {}",
result.contract_version,
coven_github_api::HEADLESS_CONTRACT_VERSION
);
}
Ok(result)
}

Expand Down Expand Up @@ -622,10 +629,39 @@ fn task_issue_number(kind: &TaskKind) -> Option<u64> {
}
}

#[cfg(test)]
mod result_tests {
use super::*;
use std::fs;

#[tokio::test]
async fn read_result_rejects_unsupported_contract_version() {
let path = std::env::temp_dir().join(format!(
"coven-github-result-version-{}.json",
uuid::Uuid::new_v4()
));
fs::write(
&path,
r#"{"contract_version":"1","status":"success","branch":null,"commits":[],"files_changed":[],"summary":"s","pr_body":"","review":{"mode":"none","evidence_status":"not_applicable","reviewed_files":[],"supporting_files":[],"findings":[],"tests_run":[],"no_findings_reason":null,"limitations":[]},"exit_reason":null}"#,
)
.expect("result fixture should be written");

let error = read_result(&path)
.await
.expect_err("v1 result must be rejected");
assert!(
format!("{error:#}").contains("unsupported result contract_version 1"),
"unexpected error: {error:#}"
);

let _ = fs::remove_file(path);
}
}

#[cfg(test)]
mod disposition_tests {
use super::*;
use coven_github_api::{CommitInfo, HEADLESS_CONTRACT_VERSION};
use coven_github_api::{CommitInfo, ReviewResult, HEADLESS_CONTRACT_VERSION};
use coven_github_config::{GitHubAppConfig, ServerConfig, WorkerConfig};
use std::path::PathBuf;

Expand All @@ -643,6 +679,7 @@ mod disposition_tests {
files_changed: vec![],
summary: "summary".to_string(),
pr_body: "body".to_string(),
review: ReviewResult::none(),
exit_reason: None,
}
}
Expand Down Expand Up @@ -828,7 +865,7 @@ mod process_tests {
/// A minimal contract-valid result.json with the given status/exit_reason.
fn result_json(status: &str, exit_reason: &str) -> String {
format!(
r#"{{"contract_version":"1","status":"{status}","branch":null,"commits":[],"files_changed":[],"summary":"s","pr_body":"","exit_reason":{exit_reason}}}"#
r#"{{"contract_version":"2","status":"{status}","branch":null,"commits":[],"files_changed":[],"summary":"s","pr_body":"","review":{{"mode":"none","evidence_status":"not_applicable","reviewed_files":[],"supporting_files":[],"findings":[],"tests_run":[],"no_findings_reason":null,"limitations":[]}},"exit_reason":{exit_reason}}}"#
)
}

Expand Down
20 changes: 19 additions & 1 deletion crates/worker/tests/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
//! If these fail, either the runtime contract drifted or the docs are stale —
//! fix one of them, do not just bless the test.

use coven_github_api::{ExitReason, SessionResult, SessionStatus, HEADLESS_CONTRACT_VERSION};
use coven_github_api::{
ExitReason, ReviewEvidenceStatus, ReviewMode, SessionResult, SessionStatus,
HEADLESS_CONTRACT_VERSION,
};
use coven_github_worker::brief::SessionBrief;

fn fixture(name: &str) -> String {
Expand Down Expand Up @@ -43,6 +46,11 @@ fn golden_result_deserializes_into_adapter_type() {

assert_eq!(result.contract_version, HEADLESS_CONTRACT_VERSION);
assert_eq!(result.status, SessionStatus::Success);
assert_eq!(result.review.mode, ReviewMode::None);
assert_eq!(
result.review.evidence_status,
ReviewEvidenceStatus::NotApplicable
);
assert_eq!(result.branch.as_deref(), Some("cody/fix-issue-42"));
assert_eq!(result.commits.len(), 1);
assert_eq!(
Expand All @@ -63,6 +71,16 @@ fn result_without_contract_version_defaults_to_current() {
"files_changed": [],
Comment thread
CompleteDotTech marked this conversation as resolved.
"summary": "Could not reproduce.",
"pr_body": "",
"review": {
"mode": "none",
"evidence_status": "not_applicable",
"reviewed_files": [],
"supporting_files": [],
"findings": [],
"tests_run": [],
"no_findings_reason": null,
"limitations": []
},
"exit_reason": "ambiguous_spec"
}"#;
let result: SessionResult = serde_json::from_str(raw).expect("legacy result must parse");
Expand Down
43 changes: 43 additions & 0 deletions deploy/coven-github/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# coven-github hosted dogfood adapter

This directory tracks the Python adapter used by the hosted coven-github
dogfood GitHub App.

The adapter is intentionally deployment-specific. It is not the canonical Rust
worker implementation; it exists so hosted dogfood behavior can be reviewed,
reproduced, and changed through PRs instead of invisible server edits.

## Files

- `coven_github_adapter.py` - webhook routing helpers, task runner, PR evidence
capture, Codex-backed headless runtime invocation, and dogfood comment
publisher.

## Runtime inputs

The deployment expects secrets and mutable state to be supplied outside git:

- `GITHUB_APP_PRIVATE_KEY_PATH` or `.coven-github-private-key.pem`
- `GITHUB_APP_ID`
- `GITHUB_WEBHOOK_SECRET`
- `COVEN_GITHUB_STATE_DIR`
- `COVEN_GITHUB_POLICY_PATH`
- `COVEN_CODE_BIN`
- `COVEN_REVIEW_FIX_LOOPS` - optional bounded review-fix loop count, clamped
between `0` and `5`; defaults to `0` so hosted repair loops are opt-in
- Codex OAuth tokens under the deployed account's `.coven-code` directory

Do not commit private keys, webhook secrets, OAuth tokens, generated task state,
workspaces, or attempt artifacts.

## Current dogfood behavior

- Emits headless contract v2 session briefs.
- Captures PR checkout metadata and changed-file patches before invoking
`coven-code`.
- Publishes visible structured review evidence, including `reviewed_files`,
`supporting_files`, findings, test evidence, no-findings rationale, and
limitations.
- When `COVEN_REVIEW_FIX_LOOPS` is greater than `0`, reruns `coven-code` with
prior structured review findings as explicit repair instructions until no
findings remain or the configured loop count is exhausted.
Loading
Loading