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
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,14 @@ jobs:
# inside the surface ADR-0007 keeps mechanical and self-contained. It imports
# only Node builtins and therefore runs with no `bun install`: a broken
# dependency graph cannot take the sign-off gate down with it.
#
# **Advisory since ADR-0035.** This job runs from the pull request's own
# checkout, so the pull request can edit both this step and the script it
# calls (#137, measured on #98). It is kept because it is faster and reports
# first, and because it can only ever fail *open* while the authoritative
# `trusted-dco` job in `trusted-gates.yml` — executed from the default branch,
# outside the pull request's control — also has to pass. Do not treat a green
# result here as the sign-off gate.
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
permissions:
Expand Down
327 changes: 327 additions & 0 deletions .github/workflows/trusted-gates.yml

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,71 @@ Until `1.0.0`, minor releases may include breaking changes

## [Unreleased]

### Added

- **Trusted CI gates that the pull request cannot edit.** A new
`.github/workflows/trusted-gates.yml` runs on `pull_request_target`, which
GitHub executes from the repository's default branch — workflow file,
referenced actions, and `actions/checkout` commit alike. `trusted-dco` is now
the authoritative sign-off gate, reading the pull request's commits as fetched
git objects that are never checked out or executed; the `dco` job in `ci.yml`
is retained as a faster advisory report that can only fail open. `gate-integrity`
blocks any change under `.github/workflows/`, `.github/actions/`, `scripts/`,
`packages/ci/`, or any of the three locations GitHub resolves `CODEOWNERS` from,
unless a maintainer applies the `gate-change-acknowledged` label — which
requires triage or write access, and which is dismissed automatically on every
event that can move the head or the base (an exclusion list: only `labeled` and
`unlabeled` do not dismiss), so an acknowledgment authorizes the state it was
given for and not the one that follows it
([#137](https://github.com/mbeacom/adrkit/issues/137),
[ADR-0035](docs/adr/0035-execute-the-gates-that-certify-a-pull-request-from-the-default-branch.md)).
The workflow deliberately has no workflow-level concurrency group: GitHub
replaces an existing pending run when a newer run enters the group even when
`cancel-in-progress` is `false`, which let a title edit replace the pending
run that had to dismiss a stale acknowledgment. Overlapping runs remain
fail-closed because check runs bind to their event head SHA, while live API
reads, dismissal verification, and changed-file completeness checks abort or
block on inconsistent state.
Scope is stated rather than overstated: this closes name-shadowing and protects
the trusted gates' definition, but it does **not** make the advisory gates in
`ci.yml` tamper-proof — they execute pull-request code and reach it through
`bun run <name>`, so the root manifest can redirect them. The specific
unprotected routes are enumerated in `DOCUMENTED_UNPROTECTED_ROUTES` and pinned
by a test.

- **`scripts/check-gate-integrity.ts`**, backing that second gate. Imports Node
builtins only, so it runs with no `bun install` and a broken dependency graph
cannot take it down. Its pass condition is an absence, so it refuses to report
a pass over an empty changed-file list, over a list the GitHub API truncated,
or over a payload it could not parse — each of those observed firing before the
check counted as coverage
([ADR-0016](docs/adr/0016-require-every-check-to-be-observed-failing-before-it-counts-as-coverage.md)).
It reads a rename's `previous_filename` as well as its `filename`, because the
files endpoint reports only the new path and a rename out of a protected prefix
would otherwise have passed clean and deleted the gate on merge.

- **[`docs/repository-trust-operations.md`](docs/repository-trust-operations.md)**,
separating the controls that are active from the ones that cannot be applied
until this lands, with the exact verified commands and the evidence for each.

### Changed

- **Actions must now be pinned to a full-length commit SHA** at the repository
level (`sha_pinning_required`, `false` → `true`). Every action here was already
SHA-pinned, so no workflow changed; the setting removes the ability to
introduce a mutable tag later.

- **`CODEOWNERS` names the gate-defining paths explicitly**, with both caveats
stated in the file: the default `*` line already covered them, and with no
`pull_request` rule on the `main` ruleset these lines request a review rather
than requiring one. ADR-0035 records why required review is not available to
this repository as a real control rather than shipping a rule that only looks
like one.

- **`scripts/check-dco.ts` no longer carries a "known limitation" note.** It now
states which invocation is the authority and which is advisory, because the
limitation stopped being true for the one that gates the merge.

## [0.11.0] - 2026-08-26

### Added
Expand Down
29 changes: 29 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,32 @@
/LICENSE @mbeacom
/NOTICE @mbeacom
/CONTRIBUTING.md @mbeacom

# Gate-defining surfaces — changes here alter what every other check certifies
#
# Listed for #137 / ADR-0035. Two honest caveats, so nobody reads more into these
# lines than they carry:
#
# 1. `*` above already makes @mbeacom the owner of every path, so these entries
# add no *coverage* today. They add explicitness, and they survive a future
# narrowing of the default line — which is exactly when a gap here would be
# easiest to introduce and hardest to notice.
# 2. The `main` ruleset carries no `pull_request` rule, so CODEOWNERS currently
# *requests* a review rather than requiring one. Required review is not
# available to this repository as a real control: GitHub does not let an
# author approve their own pull request, and with a sole maintainer that
# deadlocks every self-authored change or is waived by the admin bypass that
# is already `always`. ADR-0035 records that reasoning rather than shipping a
# rule that only looks like one.
#
# The control that does bite on these paths is the `gate-integrity` job in
# `.github/workflows/trusted-gates.yml`, which runs from the default branch and
# blocks until a maintainer applies `gate-change-acknowledged`.

/.github/workflows/ @mbeacom
/.github/actions/ @mbeacom
/scripts/ @mbeacom
/packages/ci/ @mbeacom
/.github/CODEOWNERS @mbeacom
/CODEOWNERS @mbeacom
/docs/CODEOWNERS @mbeacom
2 changes: 1 addition & 1 deletion MANIFEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ adrkit/

- The ADR corpus lives in [docs/adr/](docs/adr/), with `0000-template.md` plus
numbered records.
- There are 35 files: the template plus 34 records, ids `0001`-`0034`, with 32
- There are 36 files: the template plus 35 records, ids `0001`-`0035`, with 33
accepted and 2 superseded records.
- The schema source of truth lives in
`packages/core/src/schema/adr.schema.ts`.
Expand Down
Loading
Loading