Repository navigation
feat: phase 2 adapters, config and resolver - #4
Merged
Merged
Conversation
…ansitions Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
model.PR gains IsDraft. A draft open PR gets no ready-to-merge, no-reviewer or stale-review flag; failing checks and requested changes still flag it red. Assisted-by: Claude Code:claude-opus-5-5
net/http client with an ETag cache for GETs (copied from git-green), typed APIError (401 auth, Retry-After / rate-limit backoff), and: - RecentPRs: one author's open and merged PRs, paged until GitHub has no more or the page's last PR predates since. Repo lowercased, rollup state passed through, failing CheckRuns and StatusContexts with CodeScanning and RunID, Reviewers, MergeSHA only when merged, IsDraft. - BasePRs: the PR for each missing non-default base branch, any author, as aliases in one query per round, following the stack down. - Compare: ahead/identical included, behind/diverged not, 404 unknown. - RerunFailedJobs: ported from git-green, whole-run fallback when GitHub reports no failed jobs. Assisted-by: Claude Code:claude-opus-5-5
CheckRun has no app field; GitHub rejected the whole query. The slug
lives on checkSuite{app{slug}}. Checked against a public repo's live
GraphQL schema; the fixture now uses the real shape.
Assisted-by: Claude Code:claude-opus-5-5
…res, GraphQL rate limit - RecentPRs stops paging once the last PR on a page was updated before since; the list is ordered by update time, so createdAt could stop early and miss PRs. - Checks from the github-advanced-security app count as code scanning; CodeQL Actions jobs stay ordinary re-runnable checks. - A STARTUP_FAILURE conclusion is failing; STALE is not. - A RATE_LIMITED GraphQL error is a 429 *APIError backing off until rateLimit.resetAt (now in both queries), else 60s. - rules.Thresholds.StaleReview doc notes drafts skip review flags. Assisted-by: Claude Code:claude-opus-5-5
Link takes a separate context list (the GitHub adapter's base-branch PRs). They are walked when following a stack to the default branch but never linked to a ticket and never returned as unlinked, so another author's base PR naming my key does not join my row. Assisted-by: Claude Code:claude-opus-5-5
GitHub's EXPECTED rollup state no longer counts as a failing check. It raises the new yellow FlagCheckExpected, "PR #n waiting on a required check", ordered after partial prod and before ready to merge. Assisted-by: Claude Code:claude-opus-5-5
- RecentPRs skips PRs from forks, so a fork's branch cannot hide a missing base. - The ETag cache skips bodies over 256KB, and Compare bypasses it: the resolver caches compare results by SHA pair. - RecentPRs warns when it stops at the page cap, BasePRs when the round cap leaves bases unfetched; BasePRs asks at most 20 branches a query. - A rate-limit signal with a zero or past reset backs off 60s. - Compare rejects a base or head that is not a 7-40 character hex SHA before building the URL. - A PR with more than 50 checks is a warning. Warnings are deduplicated. Assisted-by: Claude Code:claude-opus-5-5
…, approve Sources reads a pipeline's CodeStar source actions into lowercased repo -> action name, and Repos gives the list for Env.Repos. History builds each stage's deploys newest first from one read of the pipeline's executions and action executions (bounded paging), mapping revisions through the sources. A run waiting at the approval that gates a stage is an AwaitingApproval deploy carrying the token from GetPipelineState. Superseded and canceled runs and abandoned actions are dropped. Health sums desired and running tasks across an Env's ECS services and is unknown when any is missing. Approve wraps PutApprovalResult with typed stale-token and access-denied errors. IsSSOExpired spots an expired SSO session. Assisted-by: Claude Code:claude-opus-5-5
A stage retry re-runs the deploy action under the same execution ID; keep only the latest attempt so link never sees a stale failure beside it. With an sso-session profile the SDK does not wrap token refresh errors in InvalidTokenError, so IsSSOExpired also matches a failed refresh and a missing cached token by message. Assisted-by: Claude Code:claude-opus-5-5
A run superseded while waiting at the approval still deployed to Test, so drop only the actions that never finished: abandoned ones, and ones an ended run left showing InProgress (including its approval). Finished successes and failures of superseded, stopped and canceled runs stay. Assisted-by: Claude Code:claude-opus-5-5
Add model.DeployRejected. CodePipeline has no Rejected action status: a rejected (or timed out) manual approval is a Failed approval action, so aws.History turns a run whose latest approval attempt failed, and that never reached the gated stage's deploy action, into a Rejected deploy there. link treats it like a failed run (a newer run containing the PR still supersedes it), and rules names it "<account> <stage> approval rejected" on the red pipeline-failed flag. Assisted-by: Claude Code:claude-opus-5-5
Assisted-by: Claude Code:claude-opus-5-5
GetPipelineState only supplies the approval token, so its failure now keeps the history and shows the waiting approval without a token, with a warning. IsThrottled and IsAccessDenied give callers typed checks; approvalError uses IsAccessDenied. A rejected approval's flag reads "approval rejected or expired", since a timed-out approval looks the same to CodePipeline. Assisted-by: Claude Code:claude-opus-5-5
Warn when the action page cap (now 10) cuts the window short, and when a spec's deploy or approval action matches nothing. Health accepts service ARNs, approval summaries are cut to AWS's 512 characters, and the docs cover the approval-to-deploy gap, duplicate source repos, and the checked facts that make the early stop and superseded runs safe. Assisted-by: Claude Code:claude-opus-5-5
Load validates jira (https site, project keys), github (author, owner/name repos), aws accounts and envs, rejects unknown keys, and resolves tokens from token_env or token_command. Envs, StageSpecs, Services and Thresholds give the resolver what the adapters need. An env with no repos loads with a warning: history fallback is a last resort. Assisted-by: Claude Code:claude-opus-5-5
WatchedRepos is github.repos plus every env's repos, so github.repos is now
optional; Load fails only when there is nothing to watch. Durations accept
a leading day count ("2d", "1d12h"). settings.poll_interval defaults to 60s
and must be at least 15s. approval_stage's default is documented on the
field.
Assisted-by: Claude Code:claude-opus-5-5
- jira.site may have a path but no query or fragment; duplicate projects are an error. - aws.accounts[].profile is required; account and env names are trimmed, and pipeline, stage and action names must use CodePipeline's characters. - Env errors name the env: envs[i] (account/pipeline/stage).field. - Repo names follow GitHub's rules, and "." and ".." are rejected. - An approval_action equal to deploy_action in the same stage is an error, as is a service listed twice in one env (ARNs reduced to names). - "1d-23h" is rejected instead of meaning 1h. - StageSpecs returns pipelines in file order. - A relative XDG_CONFIG_HOME is ignored, quoted stderr is cut on a rune boundary, and resolveToken documents its worst-case delay and that stderr is quoted. Assisted-by: Claude Code:claude-opus-5-5
Every read now goes through GraphQL (a POST) or Compare, which skipped the cache already, so the transport cached nothing. The client keeps one plain http.Client; the resolver caches Compare results by SHA pair. Assisted-by: Claude Code:claude-opus-5-5
Poll fetches Jira, GitHub (my recent PRs plus their base-branch PRs as link context, per watched repo) and each AWS account (sources once per pipeline for the session, one History call per pipeline, health per env) concurrently, then looks up the tickets my open PRs name that MyTickets missed, and runs link and rules over the last good data. - Compare answers are cached by (repo, base, head); only Included and NotIncluded are kept. A compare error is Unknown plus a warning. - Each source, and each AWS account, keeps its previous result when it fails and reports it in Snapshot.Status: the error, SSO expiry, and a rate limit that skips the source until RetryAt. - An env with no repos in config takes them from its pipeline's sources. - Warnings are deduplicated per poll, put in the Snapshot and logged once. Assisted-by: Claude Code:claude-opus-5-5
Run polls now, then every poll interval and on each refresh, one poll at a time, and returns when ctx is canceled. FromConfig resolves both tokens once, concurrently, and builds the Jira, GitHub and per-account AWS clients. An account with no client is a status error, not a panic. Assisted-by: Claude Code:claude-opus-5-5
Fetches cut short by shutdown fail with the context's error; Poll now returns the last good data without marking sources failed, logging, or asking GitHub for compares. Assisted-by: Claude Code:claude-opus-5-5
A Jira or GitHub error worded like an expired token no longer sets SSO. Env health reaches the slots and keeps its last good value when DescribeServices fails. Assisted-by: Claude Code:claude-opus-5-5
- GitHub watches the configured repos plus every repo the known pipeline sources name, as of the start of the poll, so a repo discovered only from Sources has its PRs fetched from the next poll. - A Jira or GitHub 401 sets AdapterStatus.Auth with "token rejected" and stops calling that source until a refresh, which retries it once. - A rate-limited Compare throttles GitHub until its RetryAfter: the rest of the poll's compare misses are Unknown without a call or a further warning, and PR fetches and compares wait for RetryAt. - AWS throttling backs off the longer of one minute and the poll interval. Assisted-by: Claude Code:claude-opus-5-5
An env with no config repos whose pipeline sources were never discovered (an expired SSO session at startup) dropped out of every chain, so the other prod env alone could show "in prod". It now counts as applying with SlotUnknown for any chain with a merged PR on its way to the default branch, which rules shows as a yellow "deploy unknown". Env.Repos stays empty. AWS throttling backed off max(AWSThrottleBackoff, interval) from the poll's start, which expires just before the next poll (polls land one interval after the last one ends). It now backs off that plus one interval, from when the error is recorded, so at least one poll is skipped. Assisted-by: Claude Code:claude-opus-5-5
A failed source keeps its last good data, so a row can be behind while the header shows only that some source failed. model.Chain now carries Stale and StaleReason: set when a repo one of the chain's PRs comes from, or an env whose slot applies, did not load this poll (failed, skipped or never loaded). An env counts as loaded when both its pipeline history and its health did. Snapshot exposes the same freshness as RepoAt (by watched repo) and EnvAt (by Env.ID()): each input's last success, zero if never. Assisted-by: Claude Code:claude-opus-5-5
The resolver now owns the user's actions, so the UI talks to one thing: Approve (refuses read-only envs, empty tokens, envs with no approval configured and accounts with no client), Rerun (failed jobs of an Actions run), Transitions and Transition (Jira workflow moves). Each successful action asks Run for a poll through an internal channel, without the token retry a user's refresh does. PipelineURL builds an env's AWS console link. model.Env gains Region, ApprovalStage and ApprovalAction, filled by config.Envs() (approval_stage defaults to the env's own stage). Deployer, CodeHost and Tracker widen to the adapter methods the actions call. Assisted-by: Claude Code:claude-opus-5-5
- A refresh asks every pipeline for its sources again, so a changed pipeline is picked up; until one answers, its previous sources stay in use, so a failed rediscovery loses nothing. - Extra tickets are not looked up when MyTickets failed this poll, and a failed lookup is a warning rather than a Jira failure (a rejected token or a rate limit still stops or backs off Jira). - A GitHub 401 during compares stops GitHub like any other 401 and skips the poll's remaining compares. - The compare cache drops answers nobody asked for in 50 polls. - A rate-limited compare backs GitHub off from when the error came back, like the other sources. - warnings drops a mutex nothing needed; resolveToken's doc gives the concurrent worst case (~11s); Snapshot's doc says the UI must treat it as read-only; TestCancelledContext is TestCanceledContext; a comment notes the first poll's compare latency, left as is. Assisted-by: Claude Code:claude-opus-5-5
…hed sources
- A chain with no PRs is stale ("GitHub not refreshed") when GitHub
failed or was skipped this poll: nobody can tell whether a PR exists.
- Rerun refuses a run ID of 0 or less (a check that is not an Actions
run) without calling GitHub.
- After a refresh, a pipeline whose Sources call fails (other than an
expired SSO session or throttling) reads its history with its cached
sources, with a warning, instead of skipping the poll.
- Tests pin that a skipped source keeps dependent rows stale for the
whole backoff window, and that a 401 or rate limit from TicketsByKey
still stops or backs off Jira.
Assisted-by: Claude Code:claude-opus-5-5
Skeyelab
force-pushed
the
feat/adapters
branch
from
October 7, 2026 03:55
8308ec8 to
61d0e46
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2 of the app-green v1 plan: data sources and the polling layer, read-only plus action methods. Stacked on #3 (phase 1 core); retarget to main once #3 merges.
What's here
internal/jira: tickets by assignee and by key (status category IDs in JQL, stable category keys in the model), transitions. Malformed fields blank only that field and come back as warnings.internal/github: recent PRs via one GraphQL query per repo (paging by update time), base-branch PRs for stacks (any author, passed to link as context only), compare, rerun. Typed errors with Retry-After, GraphQL rate limits included.internal/aws: pipeline source discovery, per-stage deploy history in one read per pipeline (approval waits with tokens, rejected approvals, retries collapsed, superseded runs keep finished stages), ECS health summed per env, approve/reject, SSO/throttle/access-denied checks.internal/config: config.toml with validation that names the field, day-unit durations, repos per env, watched repos as a union, token commands that never leak tokens.internal/resolver: concurrent polling, per-source and per-account failure isolation keeping last good data, compare cache, staleness per row, backoff and auth stops, refresh, and Approve/Rerun/Transition methods that re-poll.382 tests, race-clean, golangci-lint clean. Each task had a spec and quality review plus a final phase review.
Decisions made during review
Assisted-by: Claude Code:claude-opus-5-5