Skip to content

Commit 02a09f8

Browse files
chore: sync public mirror from internal
1 parent 7be869f commit 02a09f8

8 files changed

Lines changed: 187 additions & 27 deletions

File tree

.github/BUGBOT.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Cursor Bugbot Autofix
2+
3+
## Race condition with concurrent pushes
4+
5+
Cursor Bugbot Autofix pushes commits directly to PR branches. When a human
6+
or agent is also pushing to the same branch, this causes rebase conflicts
7+
and rejected pushes.
8+
9+
### Recommended workflow
10+
11+
1. **Always `git pull --rebase` before pushing** to integrate any autofix
12+
commits that landed while you were working.
13+
2. **If a push is rejected**, fetch and check for autofix commits:
14+
```bash
15+
git fetch origin <branch>
16+
git log --oneline HEAD..origin/<branch>
17+
```
18+
3. **If an autofix commit addresses the same issue you just fixed**, skip
19+
your commit (`git rebase --skip`) and keep the autofix, or resolve the
20+
conflict in favor of whichever fix is more complete.
21+
4. **Do not force-push** to overwrite autofix commits — rebase instead.
22+
23+
### Configuration
24+
25+
Bugbot autofix behavior is configured at
26+
[cursor.com/dashboard/bugbot](https://www.cursor.com/dashboard/bugbot),
27+
not in this repo. To disable auto-push entirely and keep comment-only
28+
reviews, uncheck "Autofix" in the dashboard.
29+
30+
### When autofix is helpful
31+
32+
Autofix is most valuable for generated mirror PRs (`sync/public-release-mirror`)
33+
where no human is actively pushing. For feature branches under active
34+
development, consider disabling autofix to avoid the race.

.github/actions/setup-rust/action.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,15 @@ runs:
131131
with:
132132
workspaces: ${{ inputs.workspaces }}
133133

134+
- name: Configure Cargo network resilience
135+
shell: bash
136+
run: |
137+
# Retry transient crates.io fetch failures and bound HTTP timeouts so
138+
# spurious network errors (e.g. "download of ba/se/base64 failed") do
139+
# not fail CI runs that would otherwise pass.
140+
echo "CARGO_NET_RETRY=5" >> "$GITHUB_ENV"
141+
echo "CARGO_HTTP_TIMEOUT=30" >> "$GITHUB_ENV"
142+
134143
- name: Install cargo-llvm-cov
135144
if: ${{ inputs.install-cargo-llvm-cov == 'true' }}
136145
uses: taiki-e/install-action@d850aa816998e5cf15f67a78c7b933f2a5033f8a # v2.63.3

packages/control-plane-rs/src/a2a/ledger.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,13 @@ fn a2a_merge_history_parts(existing: Option<&Value>, candidate: &Value) -> Value
255255
return candidate.clone();
256256
};
257257
let candidate_parts = candidate.as_array();
258+
// Preserve existing parts that carry no text (attachments, data payloads).
259+
// Drop existing parts that have a `text` field — whether pure plain-text or
260+
// decorated with extra keys like `partId` — so stale summary text does not
261+
// survive alongside the refreshed transcript text.
258262
let mut merged: Vec<Value> = existing_parts
259263
.iter()
260-
.filter(|part| !a2a_history_part_is_plain_text(part))
264+
.filter(|part| !a2a_history_part_has_text(part))
261265
.cloned()
262266
.collect();
263267
if let Some(candidate_parts) = candidate_parts {
@@ -270,6 +274,13 @@ fn a2a_merge_history_parts(existing: Option<&Value>, candidate: &Value) -> Value
270274
Value::Array(merged)
271275
}
272276

277+
/// True when the part has a `text` field (plain or decorated). Used to decide
278+
/// which existing parts to drop during a transcript-driven refresh.
279+
fn a2a_history_part_has_text(part: &Value) -> bool {
280+
part.as_object()
281+
.is_some_and(|object| object.get("text").is_some_and(Value::is_string))
282+
}
283+
273284
fn a2a_history_part_is_plain_text(part: &Value) -> bool {
274285
let Some(object) = part.as_object() else {
275286
return false;

packages/control-plane-rs/src/tests.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5409,7 +5409,7 @@ async fn a2a_task_load_preserves_rich_parts_when_history_message_is_refreshed()
54095409
"contextId": "ctx-rich-parts-refresh",
54105410
"role": "ROLE_AGENT",
54115411
"parts": [
5412-
{ "text": "stale summary" },
5412+
{ "text": "stale summary", "partId": "part-stale" },
54135413
{ "data": { "attachmentId": "artifact-rich-parts" } }
54145414
]
54155415
}
@@ -5452,6 +5452,12 @@ async fn a2a_task_load_preserves_rich_parts_when_history_message_is_refreshed()
54525452
"stale summary",
54535453
"stale plain-text part must not outlive the transcript refresh"
54545454
);
5455+
assert!(
5456+
!refreshed_parts_array
5457+
.iter()
5458+
.any(|part| part.get("partId").is_some()),
5459+
"decorated text part with partId must be replaced, not preserved with stale text"
5460+
);
54555461
}
54565462

54575463
#[tokio::test(flavor = "current_thread")]

scripts/check-guardrail-regression-suite.mjs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { existsSync, readFileSync } from "node:fs";
44
import { dirname, resolve } from "node:path";
55
import process from "node:process";
6-
import { fileURLToPath, pathToFileURL } from "node:url";
6+
import { fileURLToPath } from "node:url";
77

88
const here = dirname(fileURLToPath(import.meta.url));
99
export const repoRoot = resolve(here, "..");
@@ -18,6 +18,11 @@ export const REQUIRED_GUARDRAIL_IDS = [
1818
"opaque-git-parser-state",
1919
"bounded-output-and-json-repair",
2020
"a2a-ledger-evidence-parity",
21+
"a2a-cancel-canonical-state-guard",
22+
"a2a-compound-secret-redaction",
23+
"a2a-history-rich-part-refresh",
24+
"learner-transient-quarantine-parity",
25+
"learner-promote-repair-consistency",
2126
];
2227

2328
export function loadGuardrailManifest(path = DEFAULT_GUARDRAIL_MANIFEST) {
@@ -151,6 +156,6 @@ function main() {
151156
console.log(`Guardrail regression suite covers ${result.guardrailCount} bug class(es).`);
152157
}
153158

154-
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
159+
if (import.meta.url === `file://${process.argv[1]}`) {
155160
main();
156161
}

scripts/guardrail-regression-suite.json

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,123 @@
187187
]
188188
}
189189
]
190+
},
191+
{
192+
"id": "a2a-cancel-canonical-state-guard",
193+
"title": "A2A cancel canonical state guard",
194+
"owner": "a2a",
195+
"bugClass": "canceled tasks overwritten by later stores when status aliases are not canonicalized",
196+
"why": "store_a2a_task_unless_canceled and a2a_canceled_task must canonicalize the status state before comparing so aliases like CANCELLED or STATE_CANCELED are respected and a canceled row is not overwritten.",
197+
"evidence": [
198+
{
199+
"path": "packages/control-plane-rs/src/a2a/tasks.rs",
200+
"contains": [
201+
"fn a2a_task_is_canceled",
202+
"canonical_a2a_task_state(state) == \"TASK_STATE_CANCELED\"",
203+
"a2a_task_is_canceled(existing)"
204+
]
205+
},
206+
{
207+
"path": "packages/control-plane-rs/src/tests.rs",
208+
"contains": [
209+
"a2a_task_store_preserves_canceled_task_across_canonical_state_aliases"
210+
]
211+
}
212+
]
213+
},
214+
{
215+
"id": "a2a-compound-secret-redaction",
216+
"title": "A2A compound secret redaction",
217+
"owner": "a2a",
218+
"bugClass": "compound credential metadata keys not redacted by exact-match-only secret detection",
219+
"why": "a2a_ledger_metadata_key_is_secret must match suffix forms (webhookSecret, oauthToken, apiPassword) so compound credential fields are redacted, while preserving nonSecret/nonCredentials/notASecret exceptions and token-metric carve-outs.",
220+
"evidence": [
221+
{
222+
"path": "packages/control-plane-rs/src/a2a/ledger.rs",
223+
"contains": [
224+
"fn a2a_ledger_metadata_key_has_secret_suffix",
225+
"SECRET_SUFFIXES",
226+
"NEGATION_PREFIXES"
227+
]
228+
},
229+
{
230+
"path": "packages/control-plane-rs/src/tests.rs",
231+
"contains": [
232+
"a2a_task_ledger_redacts_compound_secret_metadata_keys"
233+
]
234+
}
235+
]
236+
},
237+
{
238+
"id": "a2a-history-rich-part-refresh",
239+
"title": "A2A history rich part refresh",
240+
"owner": "a2a",
241+
"bugClass": "non-text history parts dropped or stale text preserved during transcript-driven refresh",
242+
"why": "a2a_merge_history_parts must preserve non-text parts (attachments, data payloads) while replacing all text-bearing parts (plain or decorated) with the refreshed transcript candidate so stale summary text does not survive.",
243+
"evidence": [
244+
{
245+
"path": "packages/control-plane-rs/src/a2a/ledger.rs",
246+
"contains": [
247+
"fn a2a_merge_history_parts",
248+
"fn a2a_history_part_has_text",
249+
"!a2a_history_part_has_text(part)"
250+
]
251+
},
252+
{
253+
"path": "packages/control-plane-rs/src/tests.rs",
254+
"contains": [
255+
"a2a_task_load_preserves_rich_parts_when_history_message_is_refreshed"
256+
]
257+
}
258+
]
259+
},
260+
{
261+
"id": "learner-transient-quarantine-parity",
262+
"title": "Learner transient quarantine parity",
263+
"owner": "learner",
264+
"bugClass": "transient setup failures depressing durable routing confidence across record, load, and trim paths",
265+
"why": "record_outcome, load, and trim must all exclude transient failures from pattern updates so get_label_success_rate and get_confidence_adjustment stay consistent with get_recommendations. Ambiguous transient phrases must be gated behind environment context.",
266+
"evidence": [
267+
{
268+
"path": "packages/ambient-agent-rs/src/learner.rs",
269+
"contains": [
270+
"fn is_transient_failure_reason",
271+
"has_transient_environment_context",
272+
"fn rebuild_patterns",
273+
"outcome_is_transient_failure"
274+
]
275+
},
276+
{
277+
"path": "packages/ambient-agent-rs/src/learner.rs",
278+
"contains": [
279+
"test_transient_classifier_requires_environment_context_for_product_terms",
280+
"test_recommendations_use_consistent_non_transient_stats_after_trim"
281+
]
282+
}
283+
]
284+
},
285+
{
286+
"id": "learner-promote-repair-consistency",
287+
"title": "Learner promote repair consistency",
288+
"owner": "learner",
289+
"bugClass": "same pattern qualifying for both promote and repair or using inconsistent stats",
290+
"why": "get_recommendations must use pattern_non_transient_stats for both promote and repair decisions and exclude promoted patterns from repair candidates so a single pattern cannot appear in both lists.",
291+
"evidence": [
292+
{
293+
"path": "packages/ambient-agent-rs/src/learner.rs",
294+
"contains": [
295+
"fn pattern_non_transient_stats",
296+
"promoted.contains",
297+
"problematic_pattern_stats"
298+
]
299+
},
300+
{
301+
"path": "packages/ambient-agent-rs/src/learner.rs",
302+
"contains": [
303+
"test_recommendations_do_not_promote_and_repair_the_same_pattern"
304+
]
305+
}
306+
]
190307
}
191308
]
192309
}

test/scripts/check-guardrail-regression-suite-entry.test.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.

test/workflows/tag-release.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ describe("tag-release workflow", () => {
268268
'if (( release_run_count > existing_count )) && [[ "${dispatch_status}" == "0" ]]; then',
269269
);
270270
expect(dispatchStep?.run).toContain(
271-
'if [[ "${dispatch_status}" == "0" && -n "${latest_release_run_id}" && "${latest_release_run_id}" != "${existing_latest_run_id}" ]]; then',
271+
'if [[ "${dispatch_status}" == "0" && -n "${latest_release_run_id}" && "${latest_release_run_id}" -gt "${existing_latest_run_id:-0}" ]]; then',
272272
);
273273
expect(dispatchStep?.run).toContain(
274274
"up from ${existing_count} before dispatch",

0 commit comments

Comments
 (0)