Skip to content

Commit 5359863

Browse files
feat(actions): signed-push composite action (verified commits via GitHub App) (#509)
## What Adds `.github/actions/signed-push` — an estate composite action that pushes a branch's local commits as GitHub **Verified** commits, using a **GitHub App installation token** + `createCommitOnBranch` (via `Asana/push-signed-commits`). ## Why (Arm B1) Automation commits made with a PAT or `GITHUB_TOKEN` are **Unverified**. Any branch whose ruleset enforces `required_signatures` then **cannot merge** — this is exactly why **IDApTIK #28** is `BLOCKED` right now (all 4 commits `verified=false / unknown_key`). A GitHub App authoring commits via `createCommitOnBranch` produces Verified commits GitHub signs itself, satisfying the rule. The same App also unlocks `.github/workflows` edits (Workflows: write) and `delete_repo` (Administration: write), retiring several "GitHub API won't let AI tools operate" blockers at once. ## Details - SHA-pinned: `create-github-app-token` v3.2.0 (`bcd2ba4…`), `push-signed-commits` v1 (`6e073ae…`). - README documents the one-time App setup and the caveat that PR-only / merge-queue rules are **separate** from `required_signatures` — the App may need adding to those rulesets' **bypass list**. ## ⛔ Blocked / not yet verifiable Requires the org to **register the GitHub App** and set **`APP_ID`** + **`APP_PRIVATE_KEY`** secrets before it can run. Draft until then. YAML validated (parses as a valid composite action); not runnable in CI without the App. ## Related - Immediate estate-wide unblock for `required_signatures`: register the SSH **Signing Key** so existing commits flip to Verified. - Sibling work: `hyperpolymath/scripts#90` (wiki-seed adapter), and the community feature request for a wiki API. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c65436e commit 5359863

2 files changed

Lines changed: 109 additions & 0 deletions

File tree

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!-- SPDX-License-Identifier: MPL-2.0 -->
2+
# `signed-push` — verified commits from CI/AI tooling
3+
4+
Estate composite action that pushes a branch's local commits as GitHub **Verified**
5+
commits, so they satisfy `required_signatures` branch rulesets.
6+
7+
## Why
8+
9+
Commits made by automation with a **PAT** or the default `GITHUB_TOKEN` show as
10+
**Unverified**, and any branch whose ruleset requires signed commits then **cannot
11+
merge** (e.g. `mergeStateStatus: BLOCKED`). The only reliable way for automation to
12+
produce Verified commits is the GraphQL [`createCommitOnBranch`] mutation
13+
authenticated as a **GitHub App installation** — GitHub signs those commits itself.
14+
15+
This action mints an App installation token and delegates the push to
16+
[`Asana/push-signed-commits`], which diffs local vs. remote and replays the local
17+
commits through `createCommitOnBranch`.
18+
19+
## Prerequisites (one-time, account owner)
20+
21+
1. Register a **GitHub App** on the org (Settings → Developer settings → GitHub Apps).
22+
Repository permissions: **Contents: Read & write**, **Workflows: Read & write**
23+
(to edit `.github/workflows`), optionally **Administration: Read & write** (for
24+
`delete_repo`).
25+
2. **Install** the App on the org/repos and **generate a private key** (`.pem`).
26+
3. Store **`APP_ID`** and **`APP_PRIVATE_KEY`** as org (or repo) secrets.
27+
4. If the branch ruleset also enforces **pull-request-only merges** or a **merge
28+
queue**, add the App to that ruleset's **bypass list**`required_signatures`
29+
is satisfied by this action, but those are separate rules.
30+
31+
## Usage
32+
33+
```yaml
34+
jobs:
35+
update:
36+
runs-on: ubuntu-latest
37+
permissions:
38+
contents: write
39+
steps:
40+
- uses: actions/checkout@v4
41+
- name: Make changes
42+
run: |
43+
echo "generated" >> CHANGELOG.md
44+
git config user.name "estate-bot[bot]"
45+
git config user.email "estate-bot[bot]@users.noreply.github.com"
46+
git commit -am "chore: regenerate changelog"
47+
- name: Push as Verified
48+
uses: hyperpolymath/standards/.github/actions/signed-push@main
49+
with:
50+
app-id: ${{ vars.APP_ID }}
51+
private-key: ${{ secrets.APP_PRIVATE_KEY }}
52+
# branch: defaults to the current ref
53+
```
54+
55+
## Notes
56+
57+
- Pinned to SHAs (`create-github-app-token` v3.2.0, `push-signed-commits` v1) per
58+
estate supply-chain policy.
59+
- Run **after** local commits exist in the job — the action pushes the delta
60+
between your local branch and its remote.
61+
62+
[`createCommitOnBranch`]: https://github.blog/changelog/2021-09-13-a-simpler-api-for-authoring-commits/
63+
[`Asana/push-signed-commits`]: https://github.com/Asana/push-signed-commits
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
name: Signed push (verified commits via GitHub App)
2+
description: >
3+
Re-push the current branch's local commits as GitHub-"Verified" commits using a
4+
GitHub App installation token and the createCommitOnBranch GraphQL mutation (via
5+
Asana/push-signed-commits). This satisfies `required_signatures` branch rulesets
6+
that a PAT or the default GITHUB_TOKEN cannot. Run this AFTER you have made local
7+
commits in the job (checkout -> edit -> git commit -> this action).
8+
9+
inputs:
10+
app-id:
11+
description: GitHub App ID (pass from a repository/organization variable or secret).
12+
required: true
13+
private-key:
14+
description: GitHub App private key, PEM contents (pass from a secret).
15+
required: true
16+
branch:
17+
description: Branch to push as verified (local name == remote name). Defaults to the current ref.
18+
required: false
19+
default: ''
20+
21+
runs:
22+
using: composite
23+
steps:
24+
- name: Mint GitHub App installation token
25+
id: app-token
26+
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
27+
with:
28+
app-id: ${{ inputs.app-id }}
29+
private-key: ${{ inputs.private-key }}
30+
31+
- name: Resolve branch name
32+
id: branch
33+
shell: bash
34+
env:
35+
BRANCH_INPUT: ${{ inputs.branch }}
36+
run: echo "name=${BRANCH_INPUT:-$GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
37+
38+
# createCommitOnBranch extracts authorship from the App installation credential
39+
# and auto-marks the resulting commits as "Verified". A PAT would leave them
40+
# Unverified — which is why this must be an App token, not GITHUB_TOKEN/PAT.
41+
- name: Push local commits as Verified
42+
uses: Asana/push-signed-commits@6e073ae68b04d4322be613a6cabf391acb03ca44 # v1
43+
with:
44+
github-token: ${{ steps.app-token.outputs.token }}
45+
local_branch_name: ${{ steps.branch.outputs.name }}
46+
remote_branch_name: ${{ steps.branch.outputs.name }}

0 commit comments

Comments
 (0)