App Works — GitHub permissions, tokens and webhook events
Status: Draft · Created: 2026-09-17 · Program: App Works · Closes: EXT-14
Audience: Platform/infra (GitHub App registration, OAuth app, installs), backend (token resolution), security review.
The programme's own permission inventory is APW-02-fork-lifecycle/plan.md §4.5 (plan.md:573-587), which covers
read, fork, merge-upstream, sync pull requests, disabling workflows, the Actions switch, pushing a workflow file and
webhooks. That table stops there. Several flows in APW-05 (Builds) and APW-09 (upstream pull requests) need
permissions and event subscriptions it does not list, and no programme document compares what is asked for against
what the platform actually requests today (packages/plugin/src/common/github.scopes.ts). This document is that
inventory: one row per step or flow, with the classic OAuth scope, the GitHub App permission, the fine-grained PAT
permission, the webhook event subscription, the owning epic and the wave.
Additive-only (R-26,
CONTRACTS.md:69). Nothing here removes a scope the platform already requests. Where a permission is already granted and unused by these flows (delete_repo,project), the row says so and keeps it — removing it from the OAuth request would be a subtraction and is not proposed.
0. How to read this document
| Column | What it means |
|---|---|
| Token / actor | Which credential performs the step. member = the user's own GitHub connection (OAuth or PAT); installation = a GitHub App installation token; platform = a platform-held token. |
| Classic OAuth scope | The scope list the platform must have on the acting token (GITHUB_FULL_SCOPES, packages/plugin/src/common/github.scopes.ts:26-35). |
| GitHub App permission | The repository/organization permission the App must be granted. Metadata: read is mandatory for every App. |
| Fine-grained PAT | The equivalent fine-grained repository permission. |
| Webhook event | The subscription the step needs, if any. — means the step is read- or write-driven and needs no delivery. |
| Owner · wave | The epic that implements the step and the programme wave it ships in (README.md:302-307). |
Source of the permission names. The GitHub App and fine-grained PAT column values are GitHub's own
permission names, taken from its permissions references —
Permissions required for GitHub Apps
and Permissions required for fine-grained personal access tokens.
They are recorded here as requirements to check against the live registration, not as a copy of the settings screen:
GitHub may rename or regroup a permission, and the registration itself is an operator action (§7). The classic scope
strings are quoted from this repository's own source (packages/plugin/src/common/github.scopes.ts:24,26-35).
Two rules that apply to every row:
- Token resolution is unchanged.
resolvePluginAndTokenpicks explicit token → platform customer-org PAT when storage is managed → GitHub App installation token → the user's OAuth account → plugin-settings PAT (EXISTING-SUBSTRATE.md:73,APW-02-fork-lifecycle/plan.md:586). This document does not add a resolution path; it says which permissions each step needs on whichever token resolution lands on. - A permission the token lacks is a reportable state, never a silent failure. Missing admin or App permission
must end in a named state carrying the permission name, and must never block readiness or sync
(
APW-02-fork-lifecycle/spec.md:302-303,APW-06-app-runtime/spec.md:264-267).
1. Tokens and actors
| Credential | Who it belongs to | What it can do here | Where it is set / resolved |
|---|---|---|---|
| Member OAuth (classic) | The signed-in user, granted when they wire a Work to GitHub | Everything the member can do: fork, sync, push a workflow, write secrets, open upstream pull requests | GITHUB_FULL_SCOPES (packages/plugin/src/common/github.scopes.ts:26-35); never granted at sign-in — GITHUB_LOGIN_SCOPES is read:user + user:email only (:24) |
| Member fine-grained PAT | The same member, narrower | The same steps, one repository at a time; refused for GHCR pulls (measured) | Member-supplied; the platform validates what a token may do before use (APW-05-builds/plan.md:840-844) |
| GitHub App installation token | The platform's App installed by the member or their org | Reads, workflow writes and secret writes inside the installation's repositories — never a member's identity | Installation token fallback in resolvePluginAndToken; App deliveries arrive without a per-repo webhook (APW-05-builds/plan.md:1087-1088) |
| Platform-held PAT | The platform, for a customer organization | Reads and writes inside that organization's repositories; used when storage is platform-managed | resolvePluginAndToken (EXISTING-SUBSTRATE.md:73) — never used to open an upstream pull request (APW-09-upstream-pull-requests/spec.md:252-256) |
| Fleet push credential | The platform, narrowed per repository id | contents: write only — it cannot change workflow files and cannot open a pull request by itself | EXISTING-SUBSTRATE.md:77; APW-02-fork-lifecycle/plan.md:586-587 states this epic never uses it |
| Per-App-Work image pull token | The App Work owner, one token per App Work | Pulls one private GHCR package; nothing else | APW-05-builds/spec.md:326-329, CONTRACTS.md:321 |
The Fleet push credential, as measured (2026-09-25, APW-08 T17). Its contents: write-only grant is pinned
exactly in apps/api/src/fleet/__tests__/fleet-push-credential.service.spec.ts, and both the service and the spec
carry a "never add workflows" comment tied to T17: a Fleet node pushes before the platform judges the branch
(finalizeRemotePush, then the merge gate re-judges the head), so this grant is what keeps workflow files off the
Task branch in the meantime. Protected non-workflow paths and guarded spec blocks can still reach the branch before
judgement; they execute nothing. Not yet verified live: that GitHub refuses a workflow-file push from a
contents: write installation token. Operator check: mint such a token for a scratch repository in an Ever Works
organization, push a commit touching .github/workflows/x.yml, and expect "refusing to allow a GitHub App to create
or update workflow". The Task finalize of a cloud (API-side) run (finalizeRun) no longer pushes unjudged: its push is
off by default and, when enabled, judged before the push (APW-08 plan §2.5). The agent
tool commitToRepo is behind the same switch (off: refused naming FR-12; on: a pre-push check of the call's own files)
(THREAT-MODEL.md T-03).
2. Permission matrix — one row per step or flow
Classic OAuth scopes are named as GitHub names them; repo implies read/write repository content, pull requests
and Actions secrets, and repository webhooks also need write:repo_hook on classic tokens. App permissions and
fine-grained PAT permissions are the resource names GitHub shows in its own settings screens.
| # | Step / flow | Token / actor | Classic OAuth scope | GitHub App permission | Fine-grained PAT | Webhook event | Owner · wave |
|---|---|---|---|---|---|---|---|
| 1 | Read a repository / upstream: existence, fork network, archived, allow_forking, visibility, size, licence, stars, movedFrom | member · installation · platform | repo (private repositories) | Metadata: read, Contents: read | Metadata: read, Contents: read | — | APW-02 · W0/P1 |
| 2 | Inspect a pasted URL (ownership, push access, existing fork, Blueprint match, licence preview — no side effects) | member | repo, read:org | Metadata: read, Contents: read | Metadata: read, Contents: read | — | APW-01 · W1 |
| 3 | List the member's organizations for the fork-target picker | member | read:org | Members: read (organization) | Members: read (organization) | — | APW-01 · W1 |
| 4 | Request a fork into the member's account or organization | member | repo | Administration: write (target), Contents: read | Administration: write, Contents: read | — | APW-02 · W0/P1 |
| 5 | Create a private copy (non-fork duplicate: create repository + push full history) | member | repo | Administration: write, Contents: write | Administration: write, Contents: write | — | APW-02 · W1 |
| 6 | Fast-forward a behind-only fork (merge-upstream) | member | repo | Contents: write | Contents: write | — | APW-02 · W1 |
| 7 | Open / update the sync pull request (ever-works/upstream-sync → tracked branch) | member | repo | Contents: write, Pull requests: write | Contents: write, Pull requests: write | — | APW-02 · W1 |
| 8 | Disable inherited workflows (Actions hygiene) | member | repo + repository admin role | Actions: write | Actions: write | — | APW-02 · W1 |
| 9 | Read / switch the repository Actions switch (GET/PUT /actions/permissions) | member | repo + repository admin role | Administration: write | Administration: write | — | APW-02/05 · W1 |
| 10 | Push a workflow file (.github/workflows/ever-works-build.yml, or the checks-only file) | member | workflow | Workflows: write | Workflows: write | — | APW-05 · W1 |
| 11 | Write repository Actions secrets EW_<ENV NAME> for build.args[].fromEnv | member | repo (Actions secrets are inside repo on classic tokens) | Secrets: write | Secrets: write | — | APW-05 · W1 |
| 12 | Write and then delete the per-run EW_VERIFY__PROMPTED secret (one sealed secret, one run) | member | repo | Secrets: write | Secrets: write | — | APW-05/04 · W1 |
| 13 | Write Actions variables (the existing substrate already does this) | member | repo | Variables: write | Variables: write | — | existing substrate · W0 |
| 14 | Dispatch the build workflow (manual Rebuild; verification run) | member | workflow, repo | Actions: write | Actions: write | — | APW-05 · W1 |
| 15 | Read run status and jobs (GET /actions/runs/{id}, /jobs) for status, attempt, minutes and the failing step | member | repo | Actions: read | Actions: read | workflow_run (see §3) | APW-05 · W1 |
| 16 | Download the ever-works-build-result artifact and read a failed run's log tail | member | repo | Actions: read | Actions: read | — | APW-05 · W1 |
| 17 | Read check runs for the R-9 checks (Ever Works check: {name}) | member | repo | Checks: read | Checks: read | check_run (see §3) | APW-08/05 · W1 |
| 18 | Read package visibility and the pushed image's digest (HEAD /v2/<name>/manifests/... on GHCR + the registry token endpoint) | member (classic PAT) | read:packages (not in GITHUB_FULL_SCOPES today) | Packages: read | Packages: read | — | APW-05/06 · W1 |
| 19 | Validate and use the per-App-Work image pull token (private images) | member (classic PAT) | read:packages only — the platform refuses anything broader | Packages: read | Refused — fine-grained tokens were measured to fail GHCR pulls (APW-05-builds/plan.md:840-844) | — | APW-05 · W1 |
| 20 | Install / update the repository webhook (workflow_run only) | member | write:repo_hook | Webhooks: write | Webhooks: write | the hook it installs: workflow_run | APW-05/02 · W1 |
| 21 | Open an upstream pull request from the member's fork into the upstream | member only — a user token, never an installation or platform token | repo (+ read:org when the fork lives in an organization) | Not usable: an installation token is forbidden by FR-24 | Contents: read (fork + upstream), Pull requests: write | — | APW-09 · W2 |
| 22 | Prepare the upstream branch in the fork (upstream-pr/{slug}-{4 hex}, one commit, no sign-off) | member | repo | Contents: write | Contents: write | — | APW-09 · W2 |
| 23 | Poll upstream pull-request status, checks and reviews (upstream sends the platform no events) | member | repo | Pull requests: read, Checks: read | Pull requests: read, Checks: read | — (polling by design, APW-09-upstream-pull-requests/spec.md:276-278) | APW-09 · W2 |
| 24 | Push an approved review follow-up to the fork branch; delete the preparation branch on withdraw | member | repo | Contents: write | Contents: write | — | APW-09 · W2 |
| 25 | Read a runtime catalog (ever-works/templates, ever-works/platforms, Blueprint repositories) — tokenless first, authenticated fallback | platform · installation | repo only when the fallback is used | Metadata: read, Contents: read | Metadata: read, Contents: read | — | APW-03/11 · W1/P2 |
| 26 | Delete the fork or private copy when the member explicitly asks (typed owner/name, admin permission required) | member | delete_repo | Administration: write | Administration: write | — | APW-01 · W1 |
| 27 | Receive App-level deliveries: installation and repository-set changes, and push | installation (App webhook) | — | no extra permission (App-level webhook) | — | installation, installation_repositories, push (apps/api/src/ingest/github/github-webhook-dispatcher.service.ts:53) | existing substrate · W0 |
| 28 | Open the upstream pull request from the member's fork and push review follow-ups (added 2026-09-17, APW-09) | member (a user token, never an installation token) | repo (+ public_repo for a public upstream) | Contents: write, Pull requests: write | Contents: write, Pull requests: write | — | APW-09 · W2 |
| 29 | Read the repository's interaction limit — the temporary control APW-09's collaboratorsOnly refusal is derived from | member | repo | to be confirmed (APW-09 T5 spike (d) reads it with a non-admin token) | to be confirmed | — | APW-09 · W2 |
| 30 | Read the cross-repository diff the preparation verifier checks (GET /repos/{upstream}/compare/{base}...{forkOwner}:{branch}) | member | repo (a public upstream needs no token) | Contents: read | Contents: read | — | APW-09 · W2 |
| 31 | Read the maintainer opt-out marker (repository topics and a file the project documents, APW-09 FR-40) | member | repo | Metadata: read, Contents: read | Metadata: read, Contents: read | — | APW-09 · W2 |
| 28 | Detect a repository renamed, transferred, archived or deleted | any (read path) | repo | Metadata: read | Metadata: read | repository (actions: renamed, transferred, deleted, archived) — not subscribed today; the platform currently learns a rename from the provider redirect (movedFrom, APW-02-fork-lifecycle/plan.md:533-535) | APW-02/03 · W1 |
Rows 11, 12, 15, 16, 17, 18 and 21 are the EXT-14 delta — they are absent from APW-02-fork-lifecycle/plan.md
§4.5 and from packages/plugin/src/common/github.scopes.ts.
One discrepancy recorded rather than papered over.
APW-02-fork-lifecycle/plan.md§4.5 listsActions: writefor disabling a workflow, while the same plan's plugin notes record that GitHub answers a refused disable with a missing-permission error namingadministrationfor App tokens andactionsotherwise (plan.md:554-558). Both are quoted here as written; the App registration must satisfy the permission GitHub actually checks, and the owning epic — not this document — should settle which name the matrix keeps. Nothing here edits either file.
3. Webhook event subscriptions
The receiver is the existing POST /api/ingest/github/events, verified per delivery against the correct install's
webhook secret. Consumers register their own event lists on the dispatcher; adding a consumer does not change the
receiver.
| Event | Actions the programme relies on | Consumer / purpose | Subscription status today | Owner · wave |
|---|---|---|---|---|
workflow_run | requested, in_progress, completed | APW-05's new consumer creates and updates a Build by run id; the existing intake keeps normalising fields, it drops non-completed deliveries and stays as it is | Consumed already (apps/api/src/ingest/github/github-check-intake.service.ts:40, :449); APW-05 registers a second consumer (APW-05-builds/plan.md:1054-1071) and installs a per-repository hook for latency (:1081-1106) | APW-05 · W1 |
check_run | created, completed, rerequested | Check-run results for the R-9 Ever Works check: {name} runs and the Task CI auto-resume path | Consumed already (github-check-intake.service.ts:40); APW-08 reads check runs for its quality gates (R-9, CONTRACTS.md:52; APW-08-evolve-loop/spec.md:247-248) | APW-08/05 · W1 |
check_suite | completed | Roll-up signal alongside check_run | Consumed already (github-check-intake.service.ts:40) | existing substrate · W0 |
pull_request | opened, synchronize, reopened, closed | Task PR lifecycle, merge detection, delivery chain | Handled at the shared receiver (EXISTING-SUBSTRATE.md:70) | APW-08 · W1 |
push | default branch | Spec re-evaluation trigger, App-level installation sync | Handled (github-webhook-dispatcher.service.ts:53, APW-03-app-spec-and-catalog/spec.md:228-230) | APW-03 · W1 |
installation, installation_repositories | created, deleted, added, removed | GitHub App installation / repository-set sync | Handled (github-webhook-dispatcher.service.ts:53) | existing substrate · W0 |
issues, dependabot_alert | — | Community PR / incident intake (unchanged by this programme) | Handled (apps/api/src/ingest/github/github-issue-intake.service.ts:209) | existing substrate · W0 |
repository | renamed, transferred, deleted, archived, unarchived | Closing the gap between "the platform notices on the next read" and "the platform is told" | Not subscribed. The sync path already follows a renamed default branch (APW-02-fork-lifecycle/spec.md:333) and detects a moved repository through the provider redirect, so this is a latency/robustness item, not a correctness item | APW-02 · proposed |
Why
workflow_runand notpush. APW-05 installs exactlyworkflow_runon the user's repository:pushwould duplicate the sync leg andpull_requestwould start a second review loop (APW-05-builds/plan.md:1093-1096). Discovery by polling is what makes Builds correct; the hook only makes them fast (:1083-1085).
4. The delta against what the platform requests today
packages/plugin/src/common/github.scopes.ts exports two scope sets and one backward-compatible alias:
GITHUB_LOGIN_SCOPES = ['read:user', 'user:email'](:24) — sign-in only, deliberately no repo access.GITHUB_FULL_SCOPES = ['user:email', 'read:user', 'repo', 'delete_repo', 'workflow', 'write:repo_hook', 'read:org', 'project'](:26-35) — the broad set the capability flows request.GITHUB_SCOPES = GITHUB_FULL_SCOPES(:45) — the plugin's default-scope fallback when no caller supplies a list.
| Scope / permission | Requested today | Needed by App Works for | Delta |
|---|---|---|---|
read:user, user:email | yes — GITHUB_LOGIN_SCOPES:24 | nothing beyond identity | none |
repo | yes — :29 | rows 1–9, 11–17, 21–26 (content, pull requests, hooks, Actions secrets) | none — sufficient for classic OAuth |
workflow | yes — :31 | row 10, pushing a workflow file | none |
write:repo_hook | yes — :32 | row 20, installing the workflow_run hook | none |
read:org | yes — :33 | rows 2, 3, 21 (organization fork targets; organization-owned forks) | none |
delete_repo | yes — :30 | row 26 only — the explicit, typed, admin-checked fork deletion (APW-01-app-work-kind/spec.md:426-428) | Already granted and used by one flow; kept as-is (removing it would be a subtraction, R-26) |
project | yes — :34 | no App Works flow uses it | Already granted, unused here; kept as-is (R-26). Recorded so nobody "finds" it later and assumes it is load-bearing |
read:packages | no — absent from GITHUB_FULL_SCOPES | rows 18 and 19 — GHCR visibility, the digest check, and the per-App-Work pull token | Gap. Required on a classic token; the platform already validates for it (APW-05-builds/plan.md:840-844) |
App Secrets: write | no — not in any programme document before this one | rows 11, 12 — EW_<ENV NAME> and EW_VERIFY__PROMPTED | Gap. An App installation needs this permission granted and re-approved (§5) |
App Actions: read | no | rows 15, 16 — run status, jobs, logs, artifact download | Gap. The existing Actions service can dispatch and list workflows but has no run/artifact read (packages/plugins/github/src/github-actions.service.ts:194-214 returns void for a dispatch) |
App Checks: read | no | row 17 — the R-9 check runs | Gap. The read path exists (packages/plugins/github/src/github-api.service.ts:976-995, checks.listForRef) and reports an incomplete roll-up rather than a false green |
App Packages: read | no | rows 18, 19 | Gap |
App Variables: write | no (existing substrate writes variables) | row 13 | Existing-substrate gap, listed so the App registration is complete in one pass |
Fine-grained: Secrets, Actions, Checks, Packages | no | rows 11–19 | Gap — the fine-grained equivalents of the four App permissions above |
repository webhook event | no | rename / transfer / archive / delete awareness (§3) | Gap, optional — the flows work through the provider redirect today |
The connection-scope presets (packages/plugins/github/src/github.connection-scopes.ts:24-40) already state the rule
this table exists to serve: provider permissions are listed "so the platform can tell whether widening to 'Read and
write' needs the owner to re-approve the connected account", with read = read:user + user:email + repo +
read:org and write = all of GITHUB_FULL_SCOPES (:24-40).
5. Permission growth and the re-approval GitHub requires
Permissions are not silently upgradeable. When an App's or a token's access grows, the party who owns that access must approve the growth; until they do, the affected step fails and the platform must say so by name rather than degrade invisibly.
| Growth event | Who must re-approve | What the member / operator sees | Platform obligation |
|---|---|---|---|
A GitHub App gains a permission (e.g. Secrets: write, Actions: read, Checks: read, Packages: read) | Every installation owner of that App, in each environment where it is installed | GitHub's own review prompt lists the new permissions; the App keeps its previous access until approved | The affected step must answer a named missing-permission state and never block the flows that do not need it (APW-02-fork-lifecycle/spec.md:302-303, APW-06-app-runtime/spec.md:264-267; ACC-02-20, ACC-06-05) |
A classic OAuth app gains a scope (e.g. read:packages) | Every member who connected GitHub — existing tokens keep their old scopes | The next request that needs the new scope is refused; the member must reconnect to grant it | Classify as a permission problem, not a generic error (APW-02-fork-lifecycle/plan.md:524), and state what to do — reconnect (the same copy path as ACC-01-15, "Revoking GitHub mid-fork … Reconnect") |
| A fine-grained PAT is narrowed or expires | The member, by editing the token | Rows 18/19 refuse; a pull token expiring within 14 days warns on the Builds and Deploy tabs | Validate what a token may do before using it; refuse anything broader than the step needs (APW-05-builds/spec.md:326-330) |
| The repository Actions switch is turned off by the repository owner | The member (their own repository) | Builds read Blocked with the reason; Actions hygiene must never switch Actions off for the repository as a whole | APW-05-builds/spec.md:238-239, APW-02-fork-lifecycle/spec.md:295 |
| A branch protection rule appears (reviews or status checks required) | The member (their own repository) | The workflow is proposed as one pull request instead of a direct commit; at most one such pull request is open | APW-05-builds/spec.md:221-224 |
GitHub's documented behaviour behind the first row (Approving updated permissions for a GitHub App): GitHub notifies the owner of an account the App is installed on, the new permissions are reviewable and refusable, and a refusal leaves the App with its current permissions — so a flow that needs the new permission fails until it is granted, which is exactly why the platform must answer a named permission state rather than a generic error. For an App that is authorized but not installed, or for account-level permissions, GitHub does not notify at all and the App must prompt the member to re-authorize.
Installation-token caveat. Re-approval changes what an installation token can do, but it never changes whose
identity a step acts under: opening an upstream pull request remains a member-connection-only operation
(APW-09-upstream-pull-requests/spec.md:252-256), and the Fleet push credential's contents: write cannot grow into
workflow writes (EXISTING-SUBSTRATE.md:77).
6. Environments
| Environment | GitHub estate used by the programme | What is switched on / off | Grounded in |
|---|---|---|---|
| dev | A test tenant inside Ever Works with its own connected GitHub account; fixture upstreams owned by ever-works | EVER_WORKS_E2E_FAKES set in the PR lanes only; catalog ref may pin the test-only e2e branch of the listing repository | ACCEPTANCE.md:74-90, ACCEPTANCE.md:80, ACCEPTANCE.md:102 |
| stage | The same tenant model, plus the managed-tier trial | EVER_WORKS_APPS_MANAGED_ENABLED may be true here and nowhere else; EVER_WORKS_APPS_MAX_SCOPE stays verified-blueprints until Wave 3 | ACCEPTANCE.md:74-79 |
| production | Real member connections and real App Work repositories | EVER_WORKS_E2E_FAKES unset; the catalog ref pinned to a SHA or tag; works-app and EVER_WORKS_APP_WORKS_ENABLED fail closed | CONTRACTS.md:437, CONTRACTS.md:416, CONTRACTS.md:412-413 |
| All three | One GitHub App registration per environment, each with its own installations and its own permission review | A permission increase is approved per installation, per environment — approving it on stage does not approve it on production | Proposed — see §7; the environment-parity rule for flags is already in ACCEPTANCE.md:74-80 |
7. Open items and shared-file requests
read:packagesis not inGITHUB_FULL_SCOPES. Rows 18 and 19 need it on a classic token, and the platform already validates a pull token for exactly that scope (APW-05-builds/plan.md:840-844). Proposal for the lead: addread:packagesto the capability scope set in a follow-up change owned by APW-05 — additively, with the existingdelete_repoandprojectentries left exactly as they are (R-26). Nothing in this document changes that file.- The App registration list needs an owner. The four App permissions in rows 11–19 and the fine-grained equivalents must exist on the App before the corresponding steps can pass; registering them is an operator action, and the re-approval consequences are in §5. The registration itself is not a spec artifact.
repositoryevent subscription (§3) is proposed, not required: the redirect-following read path already keeps renames and transfers correct, so this is a latency improvement that should not gate Wave 1.- Per-environment App registrations (§6, last row) is stated as Proposed: no epic says whether dev/stage share
a registration with production. It belongs in the private operations repository once decided, with the pointer
recorded in
APW-02-fork-lifecycle/plan.md§4.5's successor table. - Plan cross-references —
APW-02-fork-lifecycle/plan.md:573-587,APW-05-builds/plan.mdandAPW-09-upstream-pull-requests/plan.mdshould each link this document from their permission/inventory sections. Those files are named in theEXT-14row astargetFilesbut are outside this document's write scope; the literal one-line additions are supplied to the lead in the accompanying shared-file request. - The
Actions: writevsadministrationdiscrepancy in §2's note should be settled inAPW-02-fork-lifecycle/plan.md§4.5 by whoever owns that file — it decides which permission the App registration must be granted before Actions hygiene can work at all. - Row 29's permission is unconfirmed (added 2026-09-17). GitHub documents the interaction-limits read as
admin-scoped, but APW-09's eligibility runs on the member's token, so whether a non-admin member can read it
at all — and whether a
403must read as "cannot tell" rather than "no limit" (whichAPW-09/plan.md§4 now requires) — is what APW-09 T5's spike (d) answers. Until it runs, row 29 says so rather than guessing, and the refusal it backs stays unverified.
8. Revision log
| Date | Author | Change |
|---|---|---|
| 2026-09-17 | App Works program (EXT-14) | First version: tokens and actors, 28-step permission matrix (classic OAuth · App permission · fine-grained PAT · event · owner · wave), webhook subscriptions, the delta against github.scopes.ts, re-approval rules and the environment split. |
Related documents. Program README · CONTRACTS · ACCEPTANCE · EXISTING-SUBSTRATE · THREAT-MODEL · APW-02 plan §4.5 · APW-05 plan · APW-09 plan · spec-tree verifier