Skip to content

Commit b21d10e

Browse files
romgenieBunsDevCopilot
authored
[codex] Require structured hosted reviews and repair loop (#31)
* feat(contract): require structured review results (#119) * feat(contract): record supporting review files (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * chore(hosted): track CompleteTech dogfood adapter (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * feat(hosted): add bounded review fix loop (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(hosted): address review gating and evidence gaps (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(hosted): apply review hardening (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(hosted): align review context and routing contract (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(hosted): enforce result contract before review loops (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(hosted): tighten review result validation (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(worker): enforce v2 review contract invariants (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(contract): validate result exit reasons (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(contract): close result envelope validation (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(contract): align session brief validation (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(hosted): harden review routing evidence (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * ci: run hosted adapter python tests (#119) Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(hosted): reject team-style bot mentions (#119) * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix(worker): align v2 review result contract Accepts degraded partial review results from coven-code while preserving complete clean-review validation. Aligns the v2 schemas and contract prose with coven-code PR #132. Refs #11 Refs PR #31 Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> * fix(adapter): handle missing webhook secret Return a structured 500 for missing webhook secret configuration and clarify nullable finding fields in the v2 contract docs. Refs PR #31 Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> --------- Signed-off-by: Timothy Wayne Gregg <Timothy.Gregg@complete.tech> Co-authored-by: Val Alexander <bunsthedev@gmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent b27c155 commit b21d10e

15 files changed

Lines changed: 2635 additions & 53 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@ env:
1111

1212
jobs:
1313
check:
14-
name: cargo check + clippy + test
14+
name: python + cargo check + clippy + test
1515
runs-on: ubuntu-latest
1616
steps:
1717
- uses: actions/checkout@v4
1818
- uses: dtolnay/rust-toolchain@stable
1919
with:
2020
components: clippy
2121
- uses: Swatinem/rust-cache@v2
22+
- name: hosted adapter python compile
23+
run: python -B -m py_compile deploy/coven-github/coven_github_adapter.py deploy/coven-github/test_coven_github_adapter.py
24+
- name: hosted adapter python tests
25+
run: python -B -m unittest discover -s deploy/coven-github -p "test_*.py"
2226
- name: cargo check
2327
run: cargo check --all-targets
2428
- name: clippy

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ keys/
44
*.pem
55
.env
66
.DS_Store
7-
.comux*
7+
deploy/coven-github/.coven-github-private-key.pem
8+
deploy/coven-github/coven-github-policy.json
9+
deploy/coven-github/coven-github-state/
10+
.comux*

crates/github/src/lib.rs

Lines changed: 88 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,8 @@ pub const DEFAULT_API_BASE_URL: &str = "https://api.github.com";
1212

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

17-
fn default_contract_version() -> String {
18-
HEADLESS_CONTRACT_VERSION.to_string()
19-
}
2017
const GITHUB_API_VERSION: &str = "2026-03-10";
2118

2219
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -175,18 +172,18 @@ pub enum TaskKind {
175172

176173
/// Structured result envelope written by coven-code --headless.
177174
#[derive(Debug, Clone, Deserialize, Serialize)]
175+
#[serde(deny_unknown_fields)]
178176
pub struct SessionResult {
179-
/// Contract major version. Defaults to the current version when a runtime
180-
/// omits it, but conformant producers MUST emit it. See
177+
/// Contract major version. Conformant producers MUST emit it. See
181178
/// `docs/headless-contract.md`.
182-
#[serde(default = "default_contract_version")]
183179
pub contract_version: String,
184180
pub status: SessionStatus,
185181
pub branch: Option<String>,
186182
pub commits: Vec<CommitInfo>,
187183
pub files_changed: Vec<String>,
188184
pub summary: String,
189185
pub pr_body: String,
186+
pub review: ReviewResult,
190187
pub exit_reason: Option<ExitReason>,
191188
}
192189

@@ -200,11 +197,95 @@ pub enum SessionStatus {
200197
}
201198

202199
#[derive(Debug, Clone, Deserialize, Serialize)]
200+
#[serde(deny_unknown_fields)]
203201
pub struct CommitInfo {
204202
pub sha: String,
205203
pub message: String,
206204
}
207205

206+
#[derive(Debug, Clone, Deserialize, Serialize)]
207+
#[serde(deny_unknown_fields)]
208+
pub struct ReviewResult {
209+
pub mode: ReviewMode,
210+
pub evidence_status: ReviewEvidenceStatus,
211+
pub reviewed_files: Vec<String>,
212+
pub supporting_files: Vec<String>,
213+
pub findings: Vec<ReviewFinding>,
214+
pub tests_run: Vec<ReviewTestRun>,
215+
pub no_findings_reason: Option<String>,
216+
pub limitations: Vec<String>,
217+
}
218+
219+
impl ReviewResult {
220+
pub fn none() -> Self {
221+
Self {
222+
mode: ReviewMode::None,
223+
evidence_status: ReviewEvidenceStatus::NotApplicable,
224+
reviewed_files: Vec::new(),
225+
supporting_files: Vec::new(),
226+
findings: Vec::new(),
227+
tests_run: Vec::new(),
228+
no_findings_reason: None,
229+
limitations: Vec::new(),
230+
}
231+
}
232+
}
233+
234+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
235+
#[serde(rename_all = "snake_case")]
236+
pub enum ReviewMode {
237+
None,
238+
PullRequest,
239+
ReviewComment,
240+
}
241+
242+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
243+
#[serde(rename_all = "snake_case")]
244+
pub enum ReviewEvidenceStatus {
245+
NotApplicable,
246+
Complete,
247+
Partial,
248+
Missing,
249+
}
250+
251+
#[derive(Debug, Clone, Deserialize, Serialize)]
252+
#[serde(deny_unknown_fields)]
253+
pub struct ReviewFinding {
254+
pub severity: ReviewSeverity,
255+
pub file: String,
256+
pub line: Option<u64>,
257+
pub title: String,
258+
pub body: String,
259+
pub recommendation: Option<String>,
260+
}
261+
262+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
263+
#[serde(rename_all = "snake_case")]
264+
pub enum ReviewSeverity {
265+
Info,
266+
Low,
267+
Medium,
268+
High,
269+
Critical,
270+
}
271+
272+
#[derive(Debug, Clone, Deserialize, Serialize)]
273+
#[serde(deny_unknown_fields)]
274+
pub struct ReviewTestRun {
275+
pub command: String,
276+
pub status: ReviewTestStatus,
277+
pub output_summary: Option<String>,
278+
}
279+
280+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
281+
#[serde(rename_all = "snake_case")]
282+
pub enum ReviewTestStatus {
283+
Passed,
284+
Failed,
285+
NotRun,
286+
Unknown,
287+
}
288+
208289
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
209290
#[serde(rename_all = "snake_case")]
210291
pub enum ExitReason {

crates/github/src/tasks.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ mod tests {
191191
files_changed: vec![],
192192
summary: "Done".to_string(),
193193
pr_body: "Body".to_string(),
194+
review: crate::ReviewResult::none(),
194195
exit_reason: None,
195196
},
196197
Some(9),

crates/worker/src/brief.rs

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,18 @@ use std::path::Path;
66
use coven_github_api::{Task, TaskKind, HEADLESS_CONTRACT_VERSION};
77
use coven_github_config::FamiliarConfig;
88

9-
fn default_contract_version() -> String {
10-
HEADLESS_CONTRACT_VERSION.to_string()
9+
fn deserialize_contract_version<'de, D>(deserializer: D) -> Result<String, D::Error>
10+
where
11+
D: serde::Deserializer<'de>,
12+
{
13+
let version = String::deserialize(deserializer)?;
14+
if version != HEADLESS_CONTRACT_VERSION {
15+
return Err(serde::de::Error::custom(format!(
16+
"unsupported session brief contract_version {}; expected {}",
17+
version, HEADLESS_CONTRACT_VERSION
18+
)));
19+
}
20+
Ok(version)
1121
}
1222

1323
/// The session-brief.json schema injected into coven-code --headless.
@@ -17,19 +27,25 @@ fn default_contract_version() -> String {
1727
/// write authority (comments, Check Runs, branches, PRs) stays with the adapter
1828
/// behind its publication gate. See issue #4.
1929
#[derive(Debug, Serialize, Deserialize)]
30+
#[serde(deny_unknown_fields)]
2031
pub struct SessionBrief {
2132
/// Contract major version this brief is written against. See
2233
/// `docs/headless-contract.md`.
23-
#[serde(default = "default_contract_version")]
34+
#[serde(deserialize_with = "deserialize_contract_version")]
2435
pub contract_version: String,
2536
pub trigger: String,
2637
pub repo: RepoBrief,
2738
pub task: TaskBrief,
2839
pub familiar: FamiliarBrief,
2940
pub workspace: WorkspaceBrief,
41+
#[serde(default, skip_serializing_if = "Option::is_none")]
42+
pub review_context: Option<serde_json::Value>,
43+
#[serde(default, skip_serializing_if = "Option::is_none")]
44+
pub audit_instruction: Option<String>,
3045
}
3146

3247
#[derive(Debug, Serialize, Deserialize)]
48+
#[serde(deny_unknown_fields)]
3349
pub struct RepoBrief {
3450
pub owner: String,
3551
pub name: String,
@@ -38,7 +54,7 @@ pub struct RepoBrief {
3854
}
3955

4056
#[derive(Debug, Serialize, Deserialize)]
41-
#[serde(tag = "kind", rename_all = "snake_case")]
57+
#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
4258
pub enum TaskBrief {
4359
FixIssue {
4460
issue_number: u64,
@@ -57,6 +73,7 @@ pub enum TaskBrief {
5773
}
5874

5975
#[derive(Debug, Serialize, Deserialize)]
76+
#[serde(deny_unknown_fields)]
6077
pub struct FamiliarBrief {
6178
pub id: String,
6279
pub display_name: String,
@@ -65,6 +82,7 @@ pub struct FamiliarBrief {
6582
}
6683

6784
#[derive(Debug, Serialize, Deserialize)]
85+
#[serde(deny_unknown_fields)]
6886
pub struct WorkspaceBrief {
6987
pub root: String,
7088
}
@@ -136,6 +154,8 @@ pub fn build(
136154
workspace: WorkspaceBrief {
137155
root: workspace.to_string_lossy().to_string(),
138156
},
157+
review_context: None,
158+
audit_instruction: None,
139159
}
140160
}
141161

0 commit comments

Comments
 (0)