Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions build/cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87046,12 +87046,13 @@ class PullRequestApprovalRepository {
collectDiagnostic(diagnostics, cause);
}
}
const checkClient = this.checkOctokit ?? this.octokit;
const [files, reviews, comments, headChecks, mergeChecks, headRuns, mergeRuns, rules, user] = await Promise.all([
this.allPages('files', this.octokit.rest.pulls.listFiles, { owner: target.owner, repo: target.repository, pull_number: target.pullNumber }),
this.allPages('reviews', this.octokit.rest.pulls.listReviews, { owner: target.owner, repo: target.repository, pull_number: target.pullNumber }),
this.allPages('comments', this.octokit.rest.issues.listComments, { owner: target.owner, repo: target.repository, issue_number: target.pullNumber }),
this.allPages('head-checks', this.octokit.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: headSha, filter: 'all' }),
testedMergeSha ? this.allPages('merge-checks', this.octokit.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: testedMergeSha, filter: 'all' }) : Promise.resolve([]),
this.allPages('head-checks', checkClient.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: headSha, filter: 'all' }, checkClient),
testedMergeSha ? this.allPages('merge-checks', checkClient.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: testedMergeSha, filter: 'all' }, checkClient) : Promise.resolve([]),
this.allPages('head-runs', this.octokit.rest.actions.listWorkflowRunsForRepo, { owner: target.owner, repo: target.repository, head_sha: headSha }),
testedMergeSha ? this.allPages('merge-runs', this.octokit.rest.actions.listWorkflowRunsForRepo, { owner: target.owner, repo: target.repository, head_sha: testedMergeSha }) : Promise.resolve([]),
this.effectiveRules(target.owner, target.repository, pull.base.ref, diagnostics),
Expand Down Expand Up @@ -87309,10 +87310,10 @@ class PullRequestApprovalRepository {
return { readable: false, dismissesStaleReviews: false, requiredChecks: [] };
}
}
async allPages(operation, method, params) {
async allPages(operation, method, params, client = this.octokit) {
return (0, approval_evidence_read_1.readApprovalEvidence)(operation, async () => {
const result = [];
for await (const response of this.octokit.paginate.iterator(method, { ...params, per_page: 100 })) {
for await (const response of client.paginate.iterator(method, { ...params, per_page: 100 })) {
const page = Array.isArray(response.data) ? response.data
: record(response.data)?.check_runs ?? record(response.data)?.workflow_runs
?? record(response.data)?.artifacts ?? record(response.data)?.jobs;
Expand Down
9 changes: 5 additions & 4 deletions build/github_action/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77558,12 +77558,13 @@ class PullRequestApprovalRepository {
collectDiagnostic(diagnostics, cause);
}
}
const checkClient = this.checkOctokit ?? this.octokit;
const [files, reviews, comments, headChecks, mergeChecks, headRuns, mergeRuns, rules, user] = await Promise.all([
this.allPages('files', this.octokit.rest.pulls.listFiles, { owner: target.owner, repo: target.repository, pull_number: target.pullNumber }),
this.allPages('reviews', this.octokit.rest.pulls.listReviews, { owner: target.owner, repo: target.repository, pull_number: target.pullNumber }),
this.allPages('comments', this.octokit.rest.issues.listComments, { owner: target.owner, repo: target.repository, issue_number: target.pullNumber }),
this.allPages('head-checks', this.octokit.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: headSha, filter: 'all' }),
testedMergeSha ? this.allPages('merge-checks', this.octokit.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: testedMergeSha, filter: 'all' }) : Promise.resolve([]),
this.allPages('head-checks', checkClient.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: headSha, filter: 'all' }, checkClient),
testedMergeSha ? this.allPages('merge-checks', checkClient.rest.checks.listForRef, { owner: target.owner, repo: target.repository, ref: testedMergeSha, filter: 'all' }, checkClient) : Promise.resolve([]),
this.allPages('head-runs', this.octokit.rest.actions.listWorkflowRunsForRepo, { owner: target.owner, repo: target.repository, head_sha: headSha }),
testedMergeSha ? this.allPages('merge-runs', this.octokit.rest.actions.listWorkflowRunsForRepo, { owner: target.owner, repo: target.repository, head_sha: testedMergeSha }) : Promise.resolve([]),
this.effectiveRules(target.owner, target.repository, pull.base.ref, diagnostics),
Expand Down Expand Up @@ -77821,10 +77822,10 @@ class PullRequestApprovalRepository {
return { readable: false, dismissesStaleReviews: false, requiredChecks: [] };
}
}
async allPages(operation, method, params) {
async allPages(operation, method, params, client = this.octokit) {
return (0, approval_evidence_read_1.readApprovalEvidence)(operation, async () => {
const result = [];
for await (const response of this.octokit.paginate.iterator(method, { ...params, per_page: 100 })) {
for await (const response of client.paginate.iterator(method, { ...params, per_page: 100 })) {
const page = Array.isArray(response.data) ? response.data
: record(response.data)?.check_runs ?? record(response.data)?.workflow_runs
?? record(response.data)?.artifacts ?? record(response.data)?.jobs;
Expand Down
29 changes: 23 additions & 6 deletions docs/pull-requests/guarded-approval.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ New `copilot setup` runs offer `recommend` by default: Copilot assesses evidence

Use `copilot setup` interactively to select an exact CI check, its GitHub App ID, and the workflow that produces it. The coverage check must enforce your repository's coverage budget; a successful advisory upload does not prove a percentage. Setup displays the selected names, target branches, Bugbot prerequisites, and branch-rule readiness before confirmation. The generated `copilot_pull_request_approval.yml` must reach the repository default branch before its `workflow_run` wakeups operate.

With `copilot setup --web`, the setup PAT can request conditional `Actions:
read` and `Checks: read` grants so the assistant can offer candidates from
completed pull-request runs from the last 90 days. Discovery requests up to
With `copilot setup --web`, the assistant can offer candidates from completed
pull-request runs from the last 90 days when the setup credential can read the
Actions and Checks APIs. GitHub currently does not expose Checks API access for
fine-grained PATs; if private-repository discovery is denied, enter the inspected
producer tuples manually. There is no selectable PAT Checks permission to add. Discovery requests up to
1,000 latest workflow runs over at most ten pages, selects up to 20 PR runs, sorts them by creation
time and inspects at most 15, with up to 100 checks per commit. Historical or
malformed dates are excluded. The lookup stops once it collects 20 recent PR
Expand Down Expand Up @@ -144,9 +146,13 @@ can also prevent the read. This state leaves existing PRs and reviews unchanged
and publishes no assessment. `invalid-policy` instead means the value was read
but failed validation: run doctor and correct the variable before retrying.

Setup includes the same evidence reads for both active modes: Actions, Contents,
Administration, Checks and Variables read, and Pull requests write to publish the
assessment card. An installation with only approval assessment does not need
Both active modes need evidence reads through Actions, Contents, Administration
and Variables, plus Pull requests write for the assessment card. Native observer
Checks reads use the job-local `GITHUB_TOKEN` supplied through
`COPILOT_APPROVAL_CHECK_TOKEN`, with the generated workflow's `checks: write`
permission; that credential also publishes the supplemental Check. The runtime
`PAT` retains bot identity, policy reads and native review writes. It does not
need an unavailable fine-grained PAT Checks grant. An installation with only approval assessment does not need
Actions, Contents or Issues write. Existing tokens created before this permission
fix need their grants checked when recommendation mode is enabled. The mode
still determines whether a native approval may be submitted.
Expand Down Expand Up @@ -180,6 +186,7 @@ check alone does not prove access to that endpoint.

| Diagnostic | Next action |
| --- | --- |
| `head-checks` / `merge-checks` denied | Restore the generated observer's `COPILOT_APPROVAL_CHECK_TOKEN: ${{ github.token }}` and job Checks permission. Do not broaden the bot PAT; fine-grained PATs cannot call the Checks API. |
| `http-401` / `http-403` | Check the runtime credential and the named endpoint access; correct the specific denial before rerunning. |
| `http-404` | Verify resource identity and visibility under that same credential. A private resource can be hidden by authorization. |
| `http-429`, supported 5xx or `transport-unavailable` | Wait for provider recovery or rate-limit reset, then make a bounded new observation. |
Expand All @@ -205,3 +212,13 @@ submission and when evidence is reread after a review POST. If GitHub might
have accepted a review, `publication-unknown` retains its review identity and
the exact failed evidence operation. Recheck after resolving that cause; the
observer reads native review history before considering another POST.


The Checks client is selected before reading evidence. If its authenticated read
is denied or returns malformed or oversized pages, the observer keeps the named
`evidence-unavailable` failure and never retries that read with the bot PAT. The
existing 3,000-record bound and head/App/workflow provenance checks still apply.
Without a job-local token, the historical PAT-only path remains available for
credentials that support Checks; it cannot convert a denied read into approval.
GitHub documents the fine-grained PAT limitation in
[Managing personal access tokens](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#fine-grained-personal-access-tokens-limitations).
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vypdev/copilot",
"version": "3.10.0",
"version": "3.10.1",
"packageManager": "pnpm@10.12.4",
"pnpm": {
"overrides": {
Expand Down
4 changes: 2 additions & 2 deletions specs/CATALOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ debt or convert unknown historic intent into a design decision.
| `cli-and-single-actions` | As-built baseline | Expose bounded local commands and workflow-dispatched operations through the shared application core | [CLI and single-action execution](./cli-and-single-action-execution.md) | 44 paths · 2026-10-19 |
| `configurable-issue-workflows` | Implemented | Select one canonical set of issue workflows and enforce its forms, dependencies, branch policy, runtime admission, migration, and diagnosis | [Configurable issue workflows and fail-closed admission](./configurable-issue-workflows-and-admission.md) | 89 paths · 2026-10-13 |
| `repository-agent-collaboration` | Implemented | Generate safe repository-local profiles, guidance, and a skill for agents contributing through configured issues, Action-managed branches, pull requests, and deployment boundaries | [Repository agent collaboration contract](./repository-agent-collaboration-contract.md) | 47 paths · 2026-10-17 |
| `guarded-pull-request-approval` | Proposed | Specify and locally implement revision-bound, evidence-gated native bot approvals for eligible human pull requests, with safe setup defaults, read-only doctor checks, and explainable recovery | [Guarded pull-request approval and setup readiness](./guarded-pull-request-approval.md) + 2 companion | 94 paths · 2026-10-20 |
| `guarded-pull-request-approval` | Proposed | Specify and locally implement revision-bound, evidence-gated native bot approvals for eligible human pull requests, with safe setup defaults, read-only doctor checks, and explainable recovery | [Guarded pull-request approval and setup readiness](./guarded-pull-request-approval.md) + 2 companion | 94 paths · 2026-10-22 |

## Evidence map

Expand Down Expand Up @@ -257,7 +257,7 @@ debt or convert unknown historic intent into a design decision.
### `guarded-pull-request-approval` — Guarded pull-request approval and setup readiness

- Owner: Copilot maintainers
- Last verified: 2026-10-20
- Last verified: 2026-10-22
- Specifications: [`specs/guarded-pull-request-approval.md`](./guarded-pull-request-approval.md) · [`specs/guarded-pull-request-approval-setup-and-doctor.md`](./guarded-pull-request-approval-setup-and-doctor.md) · [`specs/guarded-pull-request-approval-status.md`](./guarded-pull-request-approval-status.md)
- Workflows: [`.github/workflows/ci_check.yml`](../.github/workflows/ci_check.yml) · [`.github/workflows/copilot_pull_request_approval.yml`](../.github/workflows/copilot_pull_request_approval.yml) · [`setup/workflows/copilot_pull_request.yml`](../setup/workflows/copilot_pull_request.yml) · [`setup/workflows/copilot_pull_request_review_state.yml`](../setup/workflows/copilot_pull_request_review_state.yml) · [`setup/workflows/copilot_pull_request_approval.yml`](../setup/workflows/copilot_pull_request_approval.yml)
- Entrypoints: [`src/actions/github_action.ts`](../src/actions/github_action.ts) · [`src/actions/pull_request_approval_action.ts`](../src/actions/pull_request_approval_action.ts) · [`src/cli/commands/setup.ts`](../src/cli/commands/setup.ts) · [`src/cli/commands/doctor.ts`](../src/cli/commands/doctor.ts) · [`src/infrastructure/composition/pull_request_status_composition_root.ts`](../src/infrastructure/composition/pull_request_status_composition_root.ts)
Expand Down
2 changes: 1 addition & 1 deletion specs/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -2579,7 +2579,7 @@
"status": "proposed",
"scope": "Specify and locally implement revision-bound, evidence-gated native bot approvals for eligible human pull requests, with safe setup defaults, read-only doctor checks, and explainable recovery",
"owner": "Copilot maintainers",
"lastVerified": "2026-10-20",
"lastVerified": "2026-10-22",
"specs": [
"specs/guarded-pull-request-approval.md",
"specs/guarded-pull-request-approval-setup-and-doctor.md",
Expand Down
66 changes: 66 additions & 0 deletions specs/guarded-pull-request-approval.md
Original file line number Diff line number Diff line change
Expand Up @@ -485,3 +485,69 @@ MUST retain their causal diagnostic and any accepted review ID while keeping
promoted to a verified approval. This preserves read-first recovery and never
retries the review POST blindly. The three additional application cases above
cover these final-boundary risks independently of initial-read failures.


### Native Checks credential isolation — #670 (2026-10-22)

Observed 3.10.0 consumer evidence: private Platform PR #204 passed application CI
and Bugbot, but observer runs `39785896854` and `39799838760` failed with
`head-checks` / `merge-checks` HTTP 403. The adapter read Checks with the bot PAT
even though the trusted workflow supplied `COPILOT_APPROVAL_CHECK_TOKEN`. GitHub
[documents that fine-grained PATs cannot call the Checks API](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#fine-grained-personal-access-tokens-limitations).
The operator cannot add the suggested PAT permission.

The adapter MUST use the existing job-local Checks client for both head and
verified-merge Check Run pagination. Policy, bot identity, PR data, native review
POSTs and assessment comments MUST retain the bot PAT client. The native token
also retains its existing supplemental Check publication responsibility. Client
selection happens before reads; an authenticated native denial MUST NOT retry
with the PAT. Existing PAT-only callers retain their prior client selection,
bounds and non-authorizing failure behavior. No new input, permission, policy,
durable state, retry, workflow or installer behavior is introduced.

```text
trusted observer -> job-local token -> bounded head/merge Checks evidence
-> bot PAT -> policy, identity, PR/rules/coverage evidence
-> unchanged pure decision -> bot PAT native review + card
-> job-local token supplemental Check
```

Text equivalent: only Check Run inventory changes credential. The adapter still
terminates provider DTOs and sanitized errors; application/domain/presentation
contracts and all current-head, producer, coverage and native approval fences
remain unchanged. Existing architecture/workflow contracts enforce the trusted
entrypoint and credential boundaries.

Allocate **five additional distinct SDK cases**: one successful two-credential
read plus retained bot review identity; two independent head/merge denials with
no PAT fallback or review write; one malformed native page; and one native
inventory above 3,000 records. Existing PAT-only fixtures prove compatibility.
The changed adapter retains the repository's coverage gates and MUST meet at
least 95% lines/statements and 90% branches/functions. Deterministic SDK clients
replace network and waits. No new policy or UI state needs new domain cases.

Operator recovery for a Checks denial is to restore the generated native token
binding and job Checks permission, then rerun the observer. Representative
technical state remains `merge-checks (http-403)`, `evidence-unavailable`, no
authorized review, and retained prior PR/reviews. Pending, successful and partial
review states keep the existing card/summary content; there is no extra comment
or localization surface. The approval guide MUST distinguish this job credential
from the fine-grained PAT and explain manual setup producer entry when private
Checks discovery is unavailable.

Acceptance: native read fixtures must return the exact configured App/workflow
check for the verified merge; denials, malformed pages and overflow must retain
the named causal failure and perform no native review. Existing producer/head
and final reread cases must remain green. DoD includes these five cases, changed
adapter coverage, types/lint, architecture, workflow/docs/specification gates,
generated bundles/catalog and Graphify. Deploying the corrected Action and
rechecking the private consumer is required for live acceptance; local fixtures
do not establish production recovery. Rollback restores the previous client
selection without changing native reviews.

Local implementation evidence: five new cases first failed against the old
client selection and then passed; the complete repository adapter suite passes
137 cases. Full coverage passes 710 suites and 11,890 tests, with 35 existing
skips; global lines/statements/functions/branches are 97.81/96.94/97.69/94.30%.
The changed adapter reaches 98.90% lines, 97.42% statements, 100% functions and
90.82% branches. These local facts do not claim native consumer recovery.
Loading
Loading