Commit Graph

2 Commits

Author SHA1 Message Date
Adil Shaikh 19b6ca5093
feat: add provider-neutral SCM observer (#76)
* feat: add provider-neutral scm observer

* fix: satisfy scm batch query lint

* fix: avoid scm token preflight on startup

* fix: bound github scm review refresh

* docs: clarify scm observer fields

* fix: gate scm observer credentials lazily

* fix: preserve scm review threads in legacy pr writes

* fix: harden scm observer state refresh

* fix: retry scm lifecycle after persistence

* fix: persist scm lifecycle acknowledgement cursor

* fix: tune scm graphql pagination

* chore: remove scm observer no-op code

* fix: tighten scm bot and log-tail handling

* fix(scm): emit CDC events for pr_review_threads

* fix(db): renumber scm observer migration

* fix: preserve cdc triggers during scm migration

* fix: preserve scm comments across legacy writes

* fix: retry transient scm credential checks

* fix: preserve review rows during lifecycle ack

* fix: harden scm observer review follow-ups

---------

Co-authored-by: harshitsinghbhandari <24b4506@iitb.ac.in>
2026-06-04 22:26:07 +05:30
Harshit Singh Bhandari f9b08aada4
feat(scm): GitHub provider adapter — Observe(prURL) → PRObservation (#69)
* feat(scm): GitHub provider adapter — Observe(prURL) → PRObservation

A fresh GitHub SCM provider adapter under
backend/internal/adapters/scm/github/ exposing one method:

  (*Provider).Observe(ctx, prURL) (ports.PRObservation, error)

It performs a REST GET on /repos/{o}/{r}/pulls/{n} for the authoritative
draft/merged/closed/head-SHA, one GraphQL query for the reviewDecision +
mergeStateStatus + statusCheckRollup + unresolved review threads, and
(only for failure-class CheckRuns) a REST GET on
/actions/jobs/{job_id}/logs to splice the last 20 lines of the failed
job into the observation.

The package is the observation primitive; the polling loop, cadence
selection, daemon wiring, persistence and webhook receiver are all
intentionally out of scope (separate PRs / lanes).

Closes #27 — this supersedes PR #28's attempt, which targeted types
(domain.SCMProvider / SCMSnapshot / ports.SCMObserveRequest) that the
PR #62 simplification refactor has since removed. The GraphQL queries
and mergeability composition logic are credited to @whoisasx from
PR #28's provider.go; the package was re-implemented against the
current ports.PRObservation seam (post-#62) rather than rebased.

Bot-author detection uses ONLY GitHub's typed signal (__typename
"Bot" / User.Type "Bot"). The strings.Contains(login, "bot") fallback
from PR #28 was intentionally dropped — aa-18's review flagged it as
a false-positive magnet for logins like "robothon" / "lambot123".

46 table-driven tests against httptest.NewServer cover happy path,
draft, merged, closed (not merged), CI passing/failing/pending,
StatusContext legacy, log-tail extraction (and the best-effort
log-fetch failure case), mergeability mergeable/conflicting/blocked
(including ci-failing → blocked even when GitHub still says CLEAN —
the load-bearing aa-18 contract)/unstable/unknown, review
approved/changes-requested/required/none, bot-author filtering
(including the robothon false-positive guard), unresolved-only
threads, all-bots → empty Comments, ETag-304 cache hit, primary +
secondary rate-limit (with errors.As → *RateLimitError), 401 →
ErrAuthFailed, malformed JSON → Fetched:false, network error →
Fetched:false, Authorization Bearer header injection,
StaticTokenSource blank/whitespace rejection, GHTokenSource memoize
+ invalidate.

Verification:
- go build ./...               clean
- go vet ./...                 clean
- gofmt -l backend/internal/adapters/scm/   clean
- golangci-lint run ./... (v2.12, repo .golangci.yml)   0 issues
- go test -race ./internal/adapters/scm/github/...      46/46 PASS

References:
- aa-18 review of PR #28: ~/.ao/agent-reports/aa-18.md
- aa-26 tracker adapter (sibling Go-adapter pattern): #36 / agent-reports/aa-26.md

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(scm): address greptile review on #69

Four fixes from the greptile review of PR #69:

1. CI rollup pagination (P1) — when GraphQL reports
   pageInfo.hasNextPage=true for the statusCheckRollup contexts, a
   visible "all passing" set could be hiding a failing context on the
   next page. ciSummaryFromGraphQL now degrades Passing / Pending /
   Unknown to CIUnknown in that case; a known CIFailing on the visible
   page is still safe and is NOT degraded. Also bumped the per-page
   limit from 50 to 100 (GraphQL's documented max for the contexts
   connection). Two new tests pin both branches.

2. Empty GraphQL inline fragment (P2) — dropped
   `... on User { }` from the reviewThreads author selection. The
   empty selection set was technically invalid GraphQL and a future
   API tightening could reject the query. __typename already tells us
   whether the actor is a Bot, so the fragment carried no information.

3. rest.MergeStateStatus dead-code (P2) — the field decoded from the
   non-existent REST `merge_state_status` was always empty, making the
   firstNonEmpty fallback dead code. Removed the field and switched
   the tiebreaker to rest.MergeableState (the actual REST field, upper-
   cased so the same switch covers both GraphQL and REST shapes).

4. Wrong Accept header on /actions/jobs/{id}/logs (P2) — GitHub's
   REST API validates the Accept header before issuing the 302 to the
   log blob; sending text/plain risks a 406. Switched to the canonical
   application/vnd.github+json; the redirected blob serves text/plain
   regardless.

Verification:
- go build ./...               clean
- go vet ./...                 clean
- golangci-lint run ./...      0 issues
- go test -race ./internal/adapters/scm/github/...   48 / 48 PASS

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 21:44:56 +05:30