Part of #4496. P7 — medium severity, medium confidence.
Context
fileUpstreamDriftIssues (src/upstream/ruleset.ts:275+) runs from the "file-upstream-drift-issues" job, which src/index.ts:237 enqueues unconditionally inside isFullSyncWindow (index.ts:110, hour % 6 === 0) — every 6 hours, independent of whether any new drift was actually detected in that window.
For every stored open drift report (up to 20, ruleset.ts:290), it resolves the existing GitHub issue (validateRecordedGitHubIssue or findGitHubIssueForFingerprint, one GET) and then ALWAYS calls updateGitHubDriftIssue (ruleset.ts:294-313) — one PATCH — unconditionally. There is no comparison of the freshly-built payload against what's already posted before deciding to write. githubDriftIssuePayload/githubDriftIssueBody (ruleset.ts:1115-1153) are pure functions of report fields (fingerprint, severity, affectedAreas, summary, ruleset ids) with no timestamp or other always-changing field — and upsertUpstreamDriftReport upserts on the fingerprint conflict target, so an unresolved/open report with unchanged content produces byte-identical payload/body across cycles.
Net effect: for every drift report that stays open (which can be for a long time until a human resolves it), this cron path issues a redundant GET+PATCH to the same GitHub issue every 6 hours indefinitely with identical content — wasted GitHub API calls, and a PATCH also bumps the issue's "updated" timestamp and notifies watchers/assignees with no real change. Bounded to 20 reports, so not catastrophic, but a real, evidenced instance of the same "missing before-write comparison" bug class found elsewhere tonight.
Requirements
- Before calling
updateGitHubDriftIssue, compare the freshly-built payload (or a hash of it) against the previously-recorded issue body/labels/assignees for that fingerprint, and skip the PATCH when unchanged — mirroring the ai_review/ai_slop frozen-reuse pattern already used elsewhere in this codebase for the identical "don't redo unchanged work" problem.
- Ensure a genuinely changed report (severity escalated, affected areas grew, etc.) still gets a fresh PATCH — this must not accidentally suppress a real update.
- Invariant + regression tests (non-negotiable): an invariant test asserting a second
fileUpstreamDriftIssues run for an UNCHANGED open report makes zero PATCH calls; a regression test reproducing the exact bug (two consecutive 6-hour cycles with an unchanged report, asserting only the first PATCHes); a negative-path test confirming a report whose content genuinely changed (e.g. severity escalation) DOES trigger a fresh PATCH.
Deliverables
Expected outcome
Open drift issues stop getting redundant, byte-identical PATCHes every 6 hours — cutting GitHub API load and eliminating the spurious "updated" notification noise for assignees/watchers on issues that haven't actually changed.
References
src/upstream/ruleset.ts:275-313 (the unconditional PATCH loop)
src/upstream/ruleset.ts:1016-1080, 1115-1153 (the resolve helpers + the pure payload/body builders)
src/db/repositories.ts:1638-1671 (upsertUpstreamDriftReport, the fingerprint-keyed upsert)
src/index.ts:110, 237 (the 6-hour cron trigger)
Effort
S
Part of #4496. P7 — medium severity, medium confidence.
Context
fileUpstreamDriftIssues(src/upstream/ruleset.ts:275+) runs from the"file-upstream-drift-issues"job, whichsrc/index.ts:237enqueues unconditionally insideisFullSyncWindow(index.ts:110,hour % 6 === 0) — every 6 hours, independent of whether any new drift was actually detected in that window.For every stored open drift report (up to 20,
ruleset.ts:290), it resolves the existing GitHub issue (validateRecordedGitHubIssueorfindGitHubIssueForFingerprint, one GET) and then ALWAYS callsupdateGitHubDriftIssue(ruleset.ts:294-313) — one PATCH — unconditionally. There is no comparison of the freshly-built payload against what's already posted before deciding to write.githubDriftIssuePayload/githubDriftIssueBody(ruleset.ts:1115-1153) are pure functions of report fields (fingerprint, severity, affectedAreas, summary, ruleset ids) with no timestamp or other always-changing field — andupsertUpstreamDriftReportupserts on thefingerprintconflict target, so an unresolved/open report with unchanged content produces byte-identical payload/body across cycles.Net effect: for every drift report that stays open (which can be for a long time until a human resolves it), this cron path issues a redundant GET+PATCH to the same GitHub issue every 6 hours indefinitely with identical content — wasted GitHub API calls, and a PATCH also bumps the issue's "updated" timestamp and notifies watchers/assignees with no real change. Bounded to 20 reports, so not catastrophic, but a real, evidenced instance of the same "missing before-write comparison" bug class found elsewhere tonight.
Requirements
updateGitHubDriftIssue, compare the freshly-built payload (or a hash of it) against the previously-recorded issue body/labels/assignees for that fingerprint, and skip the PATCH when unchanged — mirroring theai_review/ai_slopfrozen-reuse pattern already used elsewhere in this codebase for the identical "don't redo unchanged work" problem.fileUpstreamDriftIssuesrun for an UNCHANGED open report makes zero PATCH calls; a regression test reproducing the exact bug (two consecutive 6-hour cycles with an unchanged report, asserting only the first PATCHes); a negative-path test confirming a report whose content genuinely changed (e.g. severity escalation) DOES trigger a fresh PATCH.Deliverables
updateGitHubDriftIssue, skipping the PATCH when unchangedExpected outcome
Open drift issues stop getting redundant, byte-identical PATCHes every 6 hours — cutting GitHub API load and eliminating the spurious "updated" notification noise for assignees/watchers on issues that haven't actually changed.
References
src/upstream/ruleset.ts:275-313(the unconditional PATCH loop)src/upstream/ruleset.ts:1016-1080, 1115-1153(the resolve helpers + the pure payload/body builders)src/db/repositories.ts:1638-1671(upsertUpstreamDriftReport, the fingerprint-keyed upsert)src/index.ts:110, 237(the 6-hour cron trigger)Effort
S