Skip to content

Commit 7ccee20

Browse files
authored
[guard-coverage] Add set_issue_fields to github-guard write classification and DIFC rules (#4049)
Guard coverage drifted after upstream added `set_issue_fields`: it was not classified as a mutating MCP operation and had no explicit DIFC labeling path, so it fell through default handling. This PR adds classification and labeling coverage, and incorporates review feedback to clarify scope semantics (org-level field definitions vs repo-scoped issue mutation target). - **Write-operation classification (`tools.rs`)** - Added `set_issue_fields` to `READ_WRITE_OPERATIONS`. - Added a dedicated classification test for `set_issue_fields`. - Clarified inline comments to avoid implying it is a generic org-scoped mutation. - **DIFC labeling (`tool_rules.rs`)** - Added an explicit `set_issue_fields` match arm in `apply_tool_labels` (separate from the generic granular issue PATCH group) with documented scope rationale. - Labeling remains: - secrecy: repo visibility scoped - integrity: writer scoped (`writer_integrity(repo_id, ...)`) - **Coverage hardening (`labels/mod.rs`, `tools.rs` tests)** - Added a dedicated DIFC labeling test for `set_issue_fields`. - Kept granular issue update loop tests focused on `update_issue_*` tools, with `set_issue_fields` validated independently to prevent scope ambiguity regressions. ```rust // Issue custom field mutation (field definitions are org-level; target issue is repo-scoped) "set_issue_fields", // GraphQL — sets custom field values on a specific repository issue ``` > [!WARNING] > >
2 parents 4ac7d7e + 20642f1 commit 7ccee20

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

guards/github-guard/rust-guard/src/labels/mod.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4770,6 +4770,40 @@ mod tests {
47704770
}
47714771
}
47724772

4773+
#[test]
4774+
fn test_apply_tool_labels_set_issue_fields_writer_integrity() {
4775+
let ctx = default_ctx();
4776+
let repo_id = "github/copilot";
4777+
let tool_args = json!({
4778+
"owner": "github",
4779+
"repo": "copilot",
4780+
"issue_number": 1,
4781+
"fields": [
4782+
{
4783+
"field_id": "PVTSSF_example",
4784+
"text_value": "In progress"
4785+
}
4786+
]
4787+
});
4788+
4789+
let (secrecy, integrity, _desc) = apply_tool_labels(
4790+
"set_issue_fields",
4791+
&tool_args,
4792+
repo_id,
4793+
vec![],
4794+
vec![],
4795+
String::new(),
4796+
&ctx,
4797+
);
4798+
4799+
assert_eq!(secrecy, vec![] as Vec<String>, "set_issue_fields secrecy mismatch");
4800+
assert_eq!(
4801+
integrity,
4802+
writer_integrity(repo_id, &ctx),
4803+
"set_issue_fields should have writer integrity"
4804+
);
4805+
}
4806+
47734807
#[test]
47744808
fn test_apply_tool_labels_granular_sub_issue_tools_writer_integrity() {
47754809
let ctx = default_ctx();

guards/github-guard/rust-guard/src/labels/tool_rules.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,15 @@ pub fn apply_tool_labels(
533533
integrity = writer_integrity(repo_id, ctx);
534534
}
535535

536+
// === Issue custom fields mutation (repo-scoped write) ===
537+
"set_issue_fields" => {
538+
// Field definitions are organization-level, but the mutation targets a specific
539+
// issue in owner/repo and returns issue-scoped metadata.
540+
// S = S(repo); I = writer
541+
secrecy = apply_repo_visibility_secrecy(&owner, &repo, repo_id, secrecy, ctx);
542+
integrity = writer_integrity(repo_id, ctx);
543+
}
544+
536545
// === Granular repo-scoped write operations ===
537546
// Covers granular issue PATCH tools, sub-issue management, granular PR PATCH tools,
538547
// and PR review tools. All follow: S = S(repo), I = writer.

guards/github-guard/rust-guard/src/tools.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ pub const READ_WRITE_OPERATIONS: &[&str] = &[
100100
"update_issue_title", // PATCH — modifies issue title
101101
"update_issue_type", // PATCH — modifies issue type
102102

103+
// Issue custom field mutation (field definitions are org-level; target issue is repo-scoped)
104+
"set_issue_fields", // GraphQL — sets custom field values on a specific repository issue
105+
103106
// Sub-issue management tools (alongside sub_issue_write composite)
104107
"add_sub_issue", // POST /repos/.../issues/{number}/sub_issues
105108
"remove_sub_issue", // DELETE/POST — remove sub-issue link
@@ -362,6 +365,21 @@ mod tests {
362365
}
363366
}
364367

368+
#[test]
369+
fn test_set_issue_fields_is_read_write_operation() {
370+
let op = "set_issue_fields";
371+
assert!(
372+
is_read_write_operation(op),
373+
"{} must be classified as a read-write operation",
374+
op
375+
);
376+
assert!(
377+
!is_write_operation(op),
378+
"{} should not be in WRITE_OPERATIONS (it is in READ_WRITE_OPERATIONS)",
379+
op
380+
);
381+
}
382+
365383
#[test]
366384
fn test_sub_issue_management_tools_are_read_write_operations() {
367385
for op in &["add_sub_issue", "remove_sub_issue", "reprioritize_sub_issue"] {

0 commit comments

Comments
 (0)