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
1 change: 1 addition & 0 deletions .agents/issue-index.md

Large diffs are not rendered by default.

752 changes: 752 additions & 0 deletions .agents/specs/ci-enforcement-floor.md

Large diffs are not rendered by default.

94 changes: 65 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -533,18 +533,21 @@ jobs:
set -eu
pending_args=()
if [ "$EVENT_NAME" = "pull_request" ]; then
base="$PR_BASE"
head="$PR_HEAD"
pending_args=(--pending-pr-head "$PR_HEAD")
else
# The last SUCCESSFULLY gated commit, not the previous push: a
# cancelled run must not advance the base, or its commits are
# skipped forever. That is what lets the push lane be latest-only
# (#822, #863). Falls back to `before`, today's behaviour.
base="${LAST_GREEN:-}"
[ -n "$base" ] || base="$PUSH_BASE"
head="$PUSH_HEAD"
fi
# ONE resolver for every diff-scoped base in this file (#1809). The base
# is still the last SUCCESSFULLY gated commit, so a cancelled run stays
# lossless (#822, #863). It is now clamped from below by the recorded
# enforcement floor, `scripts/ci-enforcement-floor.txt`, so a commit
# already on `main` that no contributor can repair cannot freeze the
# base and turn the walk into a ratchet.
base="$(python3 scripts/ci-walk-base.py \
--event "$EVENT_NAME" --head "$head" \
--pr-base "${PR_BASE:-}" --push-base "${PUSH_BASE:-}" \
--last-green "${LAST_GREEN:-}")"
if [ -z "$base" ]; then
echo "no diff range on the $EVENT_NAME lane: role discipline is"
echo "diff-scoped and every commit's own push already ran it."
Expand All @@ -554,6 +557,23 @@ jobs:
fi
python3 tests/scripts/test_agent_role.py
python3 tests/scripts/test_agent_onboard.py
- name: The diff-scoped gates resolve their base through one tested rule (#1809)
# Registered in TWO places, like the baseline and preflight suites above:
# here and in the `SUITES` array of `scripts/agent-preflight.sh`.
#
# The rule it covers WAS four byte-similar copies of inline shell in this
# file. `test_main_baseline.py::AgentRecordDiffRangeTests` replayed ONE of
# them, `agent-record`'s, under a shim that stubs every `python3` call --
# so it pinned which checker ran with which range string and could not
# see the base rule -- and the other three were executed by nothing. That
# is why the ratchet in #1809, an unrepairable commit freezing the base so
# every later push re-walked the same 41 violations over a range one
# commit wider, could only be found by reading a job log. The suite replays a
# cancelled-run sequence against a real throwaway repository, with the
# naive `github.event.before` base as its positive control, so the
# property #822 and #863 bought cannot be lost silently.
run: |
python3 tests/scripts/test_ci_walk_base.py
- name: Claim view, helper queue and PR reviewability
run: |
python3 scripts/claim-view.py --check
Expand Down Expand Up @@ -633,17 +653,20 @@ jobs:
run: |
set -eu
if [ "$EVENT_NAME" = "pull_request" ]; then
base="$PR_BASE"
head="$PR_HEAD"
else
# The last SUCCESSFULLY gated commit, not the previous push: a
# cancelled run must not advance the base, or its commits are
# skipped forever. That is what lets the push lane be latest-only
# (#822, #863). Falls back to `before`, today's behaviour.
base="${LAST_GREEN:-}"
[ -n "$base" ] || base="$PUSH_BASE"
head="$PUSH_HEAD"
fi
# ONE resolver for every diff-scoped base in this file (#1809). The base
# is still the last SUCCESSFULLY gated commit, so a cancelled run stays
# lossless (#822, #863). It is now clamped from below by the recorded
# enforcement floor, `scripts/ci-enforcement-floor.txt`, so a commit
# already on `main` that no contributor can repair cannot freeze the
# base and turn the walk into a ratchet.
base="$(python3 scripts/ci-walk-base.py \
--event "$EVENT_NAME" --head "$head" \
--pr-base "${PR_BASE:-}" --push-base "${PUSH_BASE:-}" \
--last-green "${LAST_GREEN:-}")"
python3 scripts/check-now-current.py --base "$base" --head "$head"
pending_args=()
if [ "$EVENT_NAME" = "pull_request" ]; then
Expand Down Expand Up @@ -725,18 +748,29 @@ jobs:
fetch-depth: 0
- name: Every new commit carries FOLLOWING_AGENTS_PROTOCOL
env:
EVENT_NAME: ${{ github.event_name }}
PR_BASE: ${{ github.event.pull_request.base.sha }}
PR_HEAD: ${{ github.event.pull_request.head.sha }}
PUSH_BASE: ${{ github.event.before }}
LAST_GREEN: ${{ needs.last-gated-commit.outputs.sha }}
PUSH_HEAD: ${{ github.sha }}
run: |
set -eu
if [ "${{ github.event_name }}" = "pull_request" ]; then
base="${{ github.event.pull_request.base.sha }}"
head="${{ github.event.pull_request.head.sha }}"
if [ "$EVENT_NAME" = "pull_request" ]; then
head="$PR_HEAD"
else
# Same self-healing base as the strict walk below (#822, #863).
base="${LAST_GREEN:-}"
[ -n "$base" ] || base="${{ github.event.before }}"
head="${{ github.sha }}"
head="$PUSH_HEAD"
fi
# ONE resolver for every diff-scoped base in this file (#1809). The base
# is still the last SUCCESSFULLY gated commit, so a cancelled run stays
# lossless (#822, #863). It is now clamped from below by the recorded
# enforcement floor, `scripts/ci-enforcement-floor.txt`, so a commit
# already on `main` that no contributor can repair cannot freeze the
# base and turn the walk into a ratchet.
base="$(python3 scripts/ci-walk-base.py \
--event "$EVENT_NAME" --head "$head" \
--pr-base "${PR_BASE:-}" --push-base "${PUSH_BASE:-}" \
--last-green "${LAST_GREEN:-}")"
# Enforce on the FIRST-PARENT mainline (the commits the submitter
# directly lands on the target branch, incl. merge commits which are
# skipped below). Merged-in feature-branch commits are owned by the
Expand Down Expand Up @@ -784,18 +818,20 @@ jobs:
run: |
set -eu
if [ "$EVENT_NAME" = "pull_request" ]; then
base="$PR_BASE"
head="$PR_HEAD"
else
# The last SUCCESSFULLY gated commit, not the previous push. A
# cancelled or failed run must not advance the base, or its commits
# are skipped forever -- that is the whole reason a superseded main
# run could not be cancelled before (#822, #863). Falls back to
# `before`, which is the previous behaviour, when nothing is found.
base="${LAST_GREEN:-}"
[ -n "$base" ] || base="$PUSH_BASE"
head="$PUSH_HEAD"
fi
# ONE resolver for every diff-scoped base in this file (#1809). The base
# is still the last SUCCESSFULLY gated commit, so a cancelled run stays
# lossless (#822, #863). It is now clamped from below by the recorded
# enforcement floor, `scripts/ci-enforcement-floor.txt`, so a commit
# already on `main` that no contributor can repair cannot freeze the
# base and turn the walk into a ratchet.
base="$(python3 scripts/ci-walk-base.py \
--event "$EVENT_NAME" --head "$head" \
--pr-base "${PR_BASE:-}" --push-base "${PUSH_BASE:-}" \
--last-green "${LAST_GREEN:-}")"
if [ -z "$base" ] || ! git cat-file -e "${base}^{commit}" 2>/dev/null; then
echo "no usable diff range on the $EVENT_NAME lane: base=${base:-<empty>}"
echo "checking the tip commit alone rather than passing vacuously."
Expand Down
1 change: 1 addition & 0 deletions scripts/agent-preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ SUITES=(
test_ltx2_dit_attn_knob_arms
test_ltx25_ab_memwatch
test_tower_skip_rss_report
test_ci_walk_base
)

failed=()
Expand Down
32 changes: 32 additions & 0 deletions scripts/ci-enforcement-floor.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# THE ENFORCEMENT FLOOR for the diff-scoped gates on `main`.
#
# One commit sha. The gates that walk `BASE..HEAD` -- the two trailer steps of
# `commit-protocol-tag`, `documentation-checkpoint`, and `agent-record`'s role
# discipline step -- never start behind it. `scripts/ci-walk-base.py` reads this
# file and takes the base as the newer of the last GREEN push run and this
# commit, decided by ancestry.
#
# WHY A FLOOR EXISTS. The base is the head of the last SUCCESSFUL push run, so a
# cancelled run is lossless: the next run simply walks wider (#822, #863). That
# is right, and it is not reverted. What it did not anticipate is a violation
# with NO AVAILABLE REMEDY -- a commit already on `main`, which can only be
# repaired by rewriting `main`. One of those freezes the base forever and the
# walk becomes a ratchet (#1809).
#
# ADVANCING IT IS A DELIBERATE, REVIEWED ACT and never a side effect. Edit this
# one line in a pull request whose body names every commit the advance forgives
# and says why each cannot be repaired. `git log -p` on this file is the
# complete history of the floor; there is no registry and no growing list.
#
# THE CURRENT VALUE forgives 42 commits dated 2026-08-13 to 2026-08-24, each one
# enumerated with its sha, date and subject in
# `.agents/specs/ci-enforcement-floor.md`. That record is the only place that
# now says those violations happened.
#
# THE FLOOR IS THE FORGIVEN COMMIT ITSELF, not the one after it. The walk is
# `FLOOR..HEAD`, which excludes `FLOOR`, so setting it to the violating commit
# forgives that commit and every commit before it, and leaves every commit
# after it enforced. Advance it to the violation, never past it: a floor set
# further forward forgives whatever lands in between, unexamined, and nothing
# in this mechanism can detect that. The review is the only control.
c00b99c7c8b64f9247230ed6220598cc5c0e347e
205 changes: 205 additions & 0 deletions scripts/ci-walk-base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#!/usr/bin/env python3
"""Resolve the BASE commit for the diff-scoped gates, clamped by a recorded floor.

`.github/workflows/ci.yml` runs four diff-scoped steps over `BASE..HEAD`: both
trailer steps of `commit-protocol-tag`, `documentation-checkpoint`, and
`agent-record`'s role-discipline step. Each one used to choose its own base in a
byte-similar copy of the same inline shell, and no test in this tree executed any
of those four blocks.

WHAT THE BASE IS. On the pull-request lane it is `pull_request.base.sha`. On the
push lane it is the head of the last SUCCESSFUL push run of this workflow,
falling back to `github.event.before`. The successful-run base is deliberate: a
cancelled run must not advance the base, or its commits are skipped and nothing
re-covers them, which is what lets the push lane be latest-only (#822, #863).

WHY IT NEEDS A FLOOR. That design assumes a green run is eventually reachable.
A commit already on `main` that violates a gate cannot be repaired, because the
only repair is a rewrite `AGENTS.md` forbids, so no green run is reachable, the
base freezes, and every later push re-walks the same violations over a range one
commit wider (#1809). The floor -- `scripts/ci-enforcement-floor.txt` -- is one
commit the walk never goes behind.

CANCELLED RUNS STAY LOSSLESS. The floor is a LOWER CLAMP on an otherwise
unchanged base. While it sits behind the last green commit, which is the steady
state because that commit advances on every green push and the floor advances
only when a human commits an advance, the resolved base is byte-identical to
what it was before this script existed. The only window that skips anything is
`last_green..floor` immediately after an advance, which is the forgiveness being
asked for and is enumerated in `.agents/specs/ci-enforcement-floor.md`.

NEWER MEANS ANCESTRY, NOT DATE. A commit date is author-controlled and can move
backwards across a rebase, so comparing dates can pick the wrong commit.
`git merge-base --is-ancestor` is the honest primitive and is what is used here.

Unit-tested by tests/scripts/test_ci_walk_base.py.
"""

from __future__ import annotations

import argparse
import re
import subprocess
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
DEFAULT_FLOOR_FILE = ROOT / "scripts/ci-enforcement-floor.txt"

SHA = re.compile(r"\A[0-9a-f]{40}\Z")


class FloorError(ValueError):
"""The recorded floor cannot be read as one commit sha."""


def read_floor(path: Path) -> str:
"""Return the single sha recorded in ``path``.

Fails closed. A floor record that cannot be read as exactly one lowercase
40-byte sha is an error rather than "no floor": silently reading a broken
record as absent restores the ratchet this file exists to break, and does it
without saying so.
"""

try:
text = path.read_text(encoding="utf-8")
except OSError as exc:
raise FloorError(f"cannot read the enforcement floor {path}: {exc}") from exc
values = [
line.strip()
for line in text.splitlines()
if line.strip() and not line.lstrip().startswith("#")
]
if len(values) != 1:
raise FloorError(
f"{path} must hold exactly one commit sha outside its comments, found {len(values)}"
)
if SHA.fullmatch(values[0]) is None:
raise FloorError(
f"{path} must hold one lowercase 40-byte commit sha, found {values[0]!r}"
)
return values[0]


def _git(repo: Path, *args: str) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["git", "-C", str(repo), *args],
text=True,
capture_output=True,
check=False,
)


def known(repo: Path, revision: str) -> bool:
"""Whether git can resolve ``revision`` to a commit object in ``repo``."""

if not revision:
return False
return _git(repo, "cat-file", "-e", f"{revision}^{{commit}}").returncode == 0


def is_ancestor(repo: Path, older: str, newer: str) -> bool:
"""Whether ``older`` is an ancestor of ``newer``. Equal commits count."""

return _git(repo, "merge-base", "--is-ancestor", older, newer).returncode == 0


def resolve_base(
*,
repo: Path,
event: str,
head: str,
pr_base: str,
push_base: str,
last_green: str,
floor: str,
warn=lambda message: print(message, file=sys.stderr),
) -> str:
"""Return the base commit the diff-scoped walk starts from."""

if event == "pull_request":
# UNCHANGED, on purpose. This lane bases on the merge base and has been
# green throughout the freeze the floor exists to end. Applying the
# floor here could only ever RAISE a base, which narrows what the lane
# enforces, and no defect asks for that.
return pr_base

base = last_green or push_base

if not base or not known(repo, base):
# The all-zero sha of a new branch, or a `before` the history no longer
# contains. The caller's own guard degrades to the tip commit alone, and
# that behaviour is preserved byte-for-byte: the floor RAISES a usable
# base and never substitutes for an unusable one.
return base

if not floor:
return base

if not known(repo, floor):
warn(f"enforcement floor {floor} is not in this checkout; base left at {base}")
return base

if not is_ancestor(repo, floor, head):
# A floor the current history does not contain cannot bound it, and
# `floor..head` across unrelated history is not a range anybody asked
# for. This is also the guard against a floor typed AHEAD of HEAD.
warn(f"enforcement floor {floor} is not an ancestor of {head}; base left at {base}")
return base

if is_ancestor(repo, floor, base):
return base

warn(f"base {base} is behind the enforcement floor; walking from {floor} instead")
return floor


def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--event", required=True, help="github.event_name")
parser.add_argument("--head", default="", help="the walk's head commit")
parser.add_argument("--pr-base", default="", help="pull_request.base.sha")
parser.add_argument("--push-base", default="", help="github.event.before")
parser.add_argument("--last-green", default="", help="head of the last successful push run")
parser.add_argument(
"--floor",
default=None,
help="override the recorded floor; an empty value means no floor",
)
parser.add_argument(
"--floor-file",
default=None,
help=f"read the floor from this file instead of {DEFAULT_FLOOR_FILE}",
)
parser.add_argument("--repo", default=".", help="repository to resolve commits in")
args = parser.parse_args(argv)

if args.floor is not None:
floor = args.floor.strip()
if floor and SHA.fullmatch(floor) is None:
print(f"--floor must be one lowercase 40-byte commit sha, got {floor!r}", file=sys.stderr)
return 2
else:
path = Path(args.floor_file) if args.floor_file else DEFAULT_FLOOR_FILE
try:
floor = read_floor(path)
except FloorError as exc:
print(str(exc), file=sys.stderr)
return 2

base = resolve_base(
repo=Path(args.repo),
event=args.event,
head=args.head,
pr_base=args.pr_base,
push_base=args.push_base,
last_green=args.last_green,
floor=floor,
)
print(base)
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading
Loading