Repository navigation
feat(ui-visual): human-only approval for golden baseline changes; auto-merge refuses without it (warren-4780) - #1356
Merged
Conversation
…o-merge refuses without it (warren-4780) auto-merge.yml gains a UI baseline approval check next to Article IX. A PR that changes scripts/ui-visual/__golden__/ or the comparison machinery arms only when a human approver applied ui-baseline-approved to the exact baseline content the head carries. The gate reads the labeler from the issue events API (never label presence), the head the approver saw from the repository activity API, and compares baseline object ids. It runs the base branch's copy of scripts/ui-visual/baseline-approval.ts, fails closed, and disarms on refusal. The arm step now also refuses to arm a head the gates did not judge. Adds the label, CODEOWNERS routing, and docs/design/ui-visual-gate.md.
jayminwest
enabled auto-merge (squash)
October 8, 2026 16:38
This was referenced Oct 8, 2026
…PRs before arming (warren-dbef) (#1360)
…e (warren-598d) (#1361) * feat(ui-visual): design-review workflow runs the evaluator on UI PRs and reports a design-review check (warren-a694) (#1359) .github/workflows/ui-design-review.yml runs the ui-design-review skill through claude-code-action (pinned by commit) after each ui-visual run, validates the verdict with scripts/design-review/verdict.ts, and reports the result as the `design-review` check run on the PR head plus one sticky comment keyed by <!-- design-review -->. - gate (pull_request_target): every PR head into main gets the check at once, success "Skipped: no src/ui changes" or queued, so the check warren-dbef makes required never hangs. - prepare (workflow_run): trusts the artifact's PR number only when that PR's head is the run's head sha, maps changed files to manifest pages, stages only in-scope screenshots and the src/ui diff. - evaluate: contents: read token, scoped tools, no web tools, fixed prompt with no PR text, model/turn/budget caps from repo variables. - report: re-validates in a fresh checkout and fails closed on a missing or invalid verdict. Logic lives in pure, unit-tested modules under scripts/design-review/. * feat(auto-merge): require ui-visual and design-review success for UI PRs before arming (warren-dbef) * docs(ui): AGENTS.md UI conventions section and a UI definition of done (warren-598d) Adds a UI conventions section to AGENTS.md that pairs each rule with the check that holds it: the Tailwind arbitrary-value ratchet, the GritQL primitive and inline-style bans, check:ui-visual, the golden baseline approval label, and the design-review rubric. Folds the UI merge-gate paragraph out of Protected paths into it and moves the repro-first section next to it. The PR template gains a collapsed UI definition of done, and src/ui/README.md links both UI design records.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important
Article IX: this PR needs a human merge. It modifies
.github/workflows/auto-merge.yml, a constitution-protected path, so the Article IX check refuses to arm auto-merge on it. That is expected and correct. Please review and merge by hand. Nothing in this PR works around that.Plan
pl-10dbstep 11 (seed warren-4780). Builds on the golden baselines from #1354 (warren-a132).Why
#1354 changed 61 baseline paths, and
auto-merge.ymlarmed it. It merged on green CI, and no gate asked a human to look at the pixels. Any agent PR can do the same thing: break a page, regenerate the goldens in the same PR, and pass. This PR closes that path.What changes
auto-merge.yml: aUI baseline approval checkstep (idbaseline) next to the Article IX check. It uses the same fail-closed contract: the token mint and the arm requirehit == 'false'from both gates.scripts/ui-visual/__golden__/**plus the files that decide how a render compares:golden-cases.ts,golden.pw.ts,golden-manifest.ts,goldens.ts,playwright.config.ts, andui-visual.yml. A looser tolerance or a skipped comparison launders a regression just as a new PNG does.ui-baseline-approvedlabel from a human approver. The gate never trusts label presence. It reads three facts from sources no bot can write:type: User, must not end in[bot], and must not have acted through a GitHub App (performed_via_github_app). The actor must not be inAUTO_MERGE_BOT_LOGIN, and must be inUI_BASELINE_APPROVERS(a repo variable, which defaults to the repo owner). An empty approver list, or one that names a bot, refuses everything.afterof the last push strictly before the label event. Commit dates are pusher-controlled, so they are never used.git show origin/<base>:scripts/ui-visual/baseline-approval.ts, the base-branch copy of the gate, so a PR cannot rewrite the gate that judges it. A PR that changes the gate script never auto-merges.Disarm auto-merge on an unapproved baseline changeturns off an earlier arm.labeledandunlabelednow trigger the workflow, filtered to this label, so approving arms right away and removing the label disarms.--match-head-commit. This closes the race between a label-event run and a newer push.Why a label and not the alternatives (full reasoning in the design record):
environmentwith required reviewers. It lives in settings outside git, where no test can check it. Each push queues a waiting deployment. The trust is the same, because an approver's token can approve a deployment through the API.author_association. The events API does not report it, andMEMBERwould admit an org-member machine account.github-actions[bot]fail theUser-type and[bot]checks. TheAUTO_MERGE_BOT_LOGINmachine account is aUser, so the gate denies it explicitly.The seed's label-triggered regenerate-and-commit workflow is deliberately not built. That workflow would put the approval before the human sees the diff, and it would add a second writer to the PR branch. The human path uses the existing dispatch mode from #1354 instead:
gh workflow run ui-visual.yml --ref <branch> -f update_goldens=true, then downloadui-goldens-<sha>into__golden__/, commit, and push. An agent may do this step.golden-diff/images inui-screenshots-<sha>. The warren-70d9 crops will appear here once that step lands.ui-baseline-approved. The label event re-runs auto-merge, which arms.Other files.
scripts/ui-visual/baseline-approval.tsholds the pure logic plus thin IO, under 400 lines, with no non-node:imports.baseline-approval.test.tscovers classification, the actor checks, head placement, and the decision.baseline-approval.workflow.test.tsruns the embedded step shell against a scratch repo, asarticle-ix-gate.test.tsdoes. It covers the base copy versus the PR copy, a crash afterhit=false, and a base that has no gate..github/labels.ymladds the label.sync-labelscreates it on merge..github/CODEOWNERSroutes review requests only and enforces nothing.docs/design/ui-visual-gate.mdis the new design record, with its catalog row.scripts/ui-visual/README.mdgains an "Approve the baselines" section.Names for warren-dbef (stable, documented in the record)
ui-visual.enable-auto-merge.protected(Article IX) andbaseline. Each writeshit=true|false.if:of the mint and arm steps.Proposed Article IX amendment (for a human to apply;
docs/CONSTITUTION.mdis not edited here)Add after the "Executable form" paragraph:
Known limits
ghas the owner must not apply the label.pull_requestworkflow runs from the PR's merge commit, so a PR that editsauto-merge.ymlcan drop the step for itself. Article IX already forces a human merge there.pr.autoMerge(reap-time arming) does not run this gate. This repo does not opt in today. If it ever does, add the baseline paths toprotectedPaths.Validation
bun run check:all --bail: 12/12 gates passed.article-ix-gate.test.ts. One existing assertion was updated for the two-gateif:.actionlintis clean onauto-merge.ymlandui-visual.yml.no-automergelabel as the approval label, withsrc/ui/as a baseline path. It approves with "approved by jayminwest at 2026-09-25T20:24:40Z (head d199896)".Follow-ups