feat: org-scoped coverage via org-owned tokens (#5) - #16
Conversation
Incomplete TDD state; resume from here. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ListOrgRepos pages through /orgs/{org}/repos; ListInstallationRepos
pages through /installation/repositories. Both return owner/name lists
that feed the report's coverage stamp.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ClassifyToken maps token prefix (ghs_/github_pat_/ghp_/gho_) and the reported X-OAuth-Scopes header to a scope class, never echoing the token itself. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o list Coverage gains token_scope_class, set via the new additive Build option WithTokenScopeClass. Validate now rejects reports whose coverage stamp lists no repos, so an empty enumeration cannot pass silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
AppAuth turns app ID + private key into a signed RS256 app JWT (stdlib only, no new deps), discovers the sole installation when none is chosen, and mints the short-lived installation token. Fixture tests verify the JWT signature server-side and pin the ambiguity error when several installations exist. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PRs authored in acme/widgets and acme/gadgets sum into one ActivitySet: per-repo counts, total, merged count, and the repo list that feeds the coverage stamp. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New flags: -org enumerates every repo of the org visible to the token; -app-id/-app-key (+optional -installation-id) mint an App installation token instead of -token; under an App token with no -repo/-org the run covers the whole installation. One run aggregates the subject's activity across all enumerated repos into a single report, and the coverage stamp carries the token scope class. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The coverage section now reads "N repositories covered" and names the credential class alongside the raw scopes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After rebasing onto main (which merged PR #14 cadence and PR #15 collaboration metrics), two gaps surfaced: 1. provider/github now fetches /pulls/{n}/reviews and /pulls/comments for every PR in every repo; the multi-repo and org-scoped fixture servers were missing those routes, so FetchActivity returned 404s. Added empty-array handlers for all PR numbers exercised by those servers. 2. resolveRepos used a plain strings.Split for the -repo flag, silently passing leading/trailing whitespace into repo names. Ported main's splitRepos trimming logic into resolveRepos so TestRunTrimsRepoListWhitespace continues to pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
b892f93 to
f4bda38
Compare
Pre-review rebaseRebased Conflicts resolved (keep-both)
Post-rebase fixups (separate commit
|
Review round 1 — approvedIndependently verified on a clean checkout of Acceptance criteria
Security scrutiny
ContractOwned new files only ( Partial failureA failing repo fetch aborts the whole run with an error — no silent skip, so the coverage stamp can never overstate what was covered. Honest behavior. Non-blocking nits (no action required)
Merging. |
Closes #5
Summary
One run with an org-scoped token now covers the subject's activity across every repository the token can see, aggregated into a single report, with a coverage stamp that makes the breadth explicit: full covered-repo list, time window, and token scope class ("covers N repos of org X, window Y–Z").
provider/github/repos.go, new):ListOrgRepospages/orgs/{org}/repos;ListInstallationRepospages/installation/repositories. Metadata only — repo names, never contents.provider/github/apptoken.go, new):AppAuthturns app ID + private key (PKCS#1 or PKCS#8 PEM) into a signed RS256 app JWT — stdlib only, zero new dependencies — auto-discovers the sole installation (or errors listing candidates when ambiguous), and mints the short-livedghs_token. Token acquisition stays pluggable: the adapter and pipeline consume "a token".provider/github/scope.go, new):ClassifyTokenmaps token prefix + reportedX-OAuth-Scopestoapp-installation/fine-grained-pat/classic-pat/oauth-app/unknown, never echoing the token.report):Coveragegainstoken_scope_class(set via new additiveBuildoptionWithTokenScopeClass);Validatenow rejects an empty coverage repo list (permitted tightening).cmd/coderepute): new additive flags-org,-app-id,-app-key,-installation-id; resolution order: explicit-repo→-orgenumeration → whole installation under an App token. New fileorgscope.goholds the plumbing.Parallel-work contract compliance
ActivitySet, the adapter's activity-fetch file (github.go), metrics package files,collaboration/cadence/verificationsub-structs, CI YAML: untouched.report/report.go(new field, variadicBuildoption — existing call sites compile unchanged, plus the explicitly permittedValidaterepo-list tightening),cmd/coderepute/main.go(new flags + wiring),render/templates/sections/50-coverage.tmpl(my territory to enrich).acme/widgets+acme/gadgetssum per-repo and in total) and end-to-end at the CLI (report sums 1 + 2 authored PRs across repos). Per the contract, no metrics files were modified; aggregation flows through the existing per-repo fetch loop andmetrics.Compute.Test output
The token exchange itself is fixture-tested: the test server cryptographically verifies the app JWT (RS256 signature against the real public key,
issclaim,exp > iat) before serving the installation token.Manual checklist — App-token live validation (real org)
No real org credentials were used in development; run this against a real org before relying on App auth:
coderepute -app-id <APP_ID> -app-key <key.pem> -subject <username> -out out/out/report.jsoncoverage.reposlists every org repo, including private ones, and nothing outside the installation.coverage.token_scope_classisapp-installation.collaboration.pull_requestsmatches their sum.-installation-id <ID>selects one.-org <org>plus a classic PAT and diff the coverage stamps (classic-patvsapp-installation).🤖 Generated with Claude Code