Development record
A dependency bump inside the bound is re-authored as the owner and only pushed by an App
Context
The attribution gate (scripts/check-attribution.sh) refuses a machine as the
author or committer of any commit, so a pull request dependabot opens is not
mergeable as authored, and every bump was landed by a person re-authoring it by
hand. The rule is deliberate: the contributor graph is built from those two
fields, and a squash merge re-appends a mis-identified branch author as a
co-author. iss-2609221820487644
asked for bumps to land without that hand step, and said that because it
reverses a stated rule it needs this record and a brief invariant.
The product thinker ruled on 2026-09-22 for route 2 of the capture's three
shapes (a workflow re-authors the bump before the gate runs; the gate does not
change) and planned it as
itd-2609221842494980,
whose ## Decisions fix the bound as the diff's shape, the message and the
opt-in. A ruling on 2026-09-29 settled the credential: a GitHub App the person
creates and installs pushes the result, and the landed commit's author is the
person. The constraint that shapes the rest is the platform's: a
pull_request run that dependabot starts reads only Dependabot secrets, and a
push made with the run's own GITHUB_TOKEN starts no checks.
Decision
We will re-author an in-bound dependency bump as the repository owner, and let a GitHub App do nothing but push it.
- The owner authors and commits; the App only pushes. The re-authored
commit has the same tree and parent as the bot's, the owner named in
.abcd/config/dependency-reauthor.confas author AND committer, a message naming the bot, its commit and the workflow, andAssisted-by: None. The App's identity appears in neither field, because the gate refuses a[bot]identity in both roles, and the gate stays unchanged. - The bound. A pull request is re-authored only when all of these hold:
its author is a bot the declaration names and the run's actor is that same
bot (a person who pushes a bot-authored commit onto the bot's branch starts
a run whose actor is that person); its branch lives in this repository and
starts with the prefix the row declares, and its name passes an allowlist
and a length cap before any message repeats it; it carries exactly one
commit, authored by that bot; and that commit only MODIFIES files the row
names, at the directory
.github/dependabot.ymldeclares for the ecosystem. A file added, deleted or renamed, or the same name in any other directory, is outside the bound, because a new manifest carves a module out of the tree the checks run over. Anything outside the bound is left alone, and the run names the clause it failed. The script and the declaration are checked out from the pull request's base, so the branch under judgement cannot change them. - The accepted residual. The bound judges which files a bump changes,
never what it writes in them: a manifest's content inside the bound (a
go.modreplace,toolchainortooldirective, ago.sumline) is re-authored with no person reading it. This follows from ruling 2 (the bound is the diff's shape) and is accepted here rather than left implicit. - The secrets are Dependabot secrets. The App's id and private key are
stored as the Dependabot secrets
DEPENDENCY_REAUTHOR_APP_IDandDEPENDENCY_REAUTHOR_APP_KEY, the only store a dependabot-startedpull_requestrun reads. The run mints a short-lived installation token scoped to this repository's contents, pushes under a lease pinned to the bot's commit, and revokes the token on exit. - Refusal, never a fallback. While the owner is unset or either secret is
absent, an in-bound bump is refused by name and stays a person's to land.
The workflow never pushes with
GITHUB_TOKENand never keeps the bot as author.
Alternatives Considered
- The gate exempts a manifest-only commit on the bot's branch. Rejected by the 2026-09-22 ruling: it puts a machine into the contributor graph and widens the one gate every change passes.
- Dependabot off, bumps by hand on a schedule. Rejected by the same ruling: it keeps the hand step the capture asked to remove.
- Push with a token the person creates. Considered first (the 2026-09-29 rulings) and refined to the App: an installation token is minted per run, scoped to one repository's contents and revoked on exit, where a personal token is long-lived and carries its owner's whole reach.
- Re-author inside the bound, the App only pushing (chosen). The gate and the rule behind it stay as they are, and the one thing a person gives up is stated: authorship asserted by a workflow for commits inside this bound and no others.
Consequences
- A bump inside the bound lands with no person re-authoring it, once the person has set the owner, created and installed the App, and stored its two secrets; until then the workflow refuses and a person lands the bump as before.
- A change to the bound, the residual or the credential's placement is a change to this record and to the brief invariant that cites it, never merely a code path.
- An Actions bump stays outside the bound (a workflow file is never a manifest) and is still landed by a person.
- The token mint is about forty lines of shell (JWT signing with
openssl, the exchange withcurl) that abcd maintains and re-audits, in exchange for no new pinned action.
Frontmatter
| id | adr-2609292116133348 |
|---|---|
| type | adr |
| status | accepted |
| date | 2026-09-29 |
.abcd/development/decisions/adrs/2609292116133348-a-dependency-bump-inside-the-bound-is-re.md
open on GitHub ↗ · commit history ↗
References1
- itd-2609221842494980A dependency bump lands without a person re-authoring it, and no machine enters the contributor graphrelated
Referenced by1
- itd-2609301020001595A pinned-action bump syncs its scaffold template and lands re-authoredrelated
mentions2
- dcn-26100200000000082026-10-02 — Ruling H2 of the person (2026-09-29, same interview and local-tier file as the DR3 entry above; recorded by lane rd2), verbatim: "dependency-update key: a GITHUB APP created and installed on the repo; the landed update's AUTHOR is the person (App pushes; human authorship kept so the attribution gate passes). Creating/installing the App and storing its secret is the person's act (owed)." Built: `.github/workflows/dependency-reauthor.yml` re-authors an in-bound bump with the owner named in `.abcd/config/dependency-reauthor.conf` as author and committer and pushes it with the App's token, minted in the shell under ruling CG1 (adr-2609292116133348). Not done: creating and installing the App and storing its two secrets is the person's act and stays owed (iss-2609292030070275).
- iss-2609221820487644The attribution gate should let a dependabot dependency bump through without a human re-authoring it. The product thinker asked for this on 2026-09-22 after PR 655 sat blocked with auto-merge armed and every other check green. Two things must be said precisely, because the request names the trailer and the trailer is not what fails. The gate refuses a bot on IDENTITY: check_ident reads dependabot[bot] as a machine in the author role on two independent signals, the forge's [bot] name suffix and the bot mailbox, and it deliberately stays silent about the missing Assisted-by trailer so the remedy is not misread, its own comment saying that adding a trailer is not the fix for a dependency bump and landing it as a human is. No review clears an identity refusal, which is why an armed auto-merge looks like it is waiting for a reviewer when it is waiting for something no reviewer can give. The rule is deliberate and stated in AGENTS.md: the contributor graph is built from the author and committer fields, a machine there asserts an authorship it does not hold, and a squash merge re-appends a mis-identified branch author as a co-author, so the consequence, that a dependabot pull request is not mergeable as authored, is written down as intended. Wanted: a way for a dependency bump to land without a person re-authoring it every time, without letting a machine into the contributor graph on any other change. Candidate shapes for the decision to weigh: the gate exempts a commit whose diff touches only go.mod and go.sum on a branch the forge marks as dependabot's, with the exemption named in the refusal it would otherwise raise; or the repository takes dependabot's bumps through a workflow that re-authors them as the human owner before the gate runs, so nothing about the gate changes; or dependabot is turned off for this repository and bumps are made by hand on a schedule. This reverses a stated rule, so it is an ADR plus a brief invariant before any code moves, not a quiet edit to the script.