ci: require human signoff before a major release - #201
Conversation
|
About to review this PR. I did want to mention that Austin added something to the stage-gate for React Web SDK that I think could be good here. youversion/platform-sdk-react#418. It makes sure to check against the right branches instead of PRs that are being merged into a feature branch |
cameronapak
left a comment
There was a problem hiding this comment.
Review
Summary
Standards: 2 must-fix. Spec: 3 must-fix. Primary concern: the gate can report success for an unsigned major release.
Review evidence
- Scope: reviewed revision, Jira and PR discussion, all changed files, repository guidance, upstream React PR #418, and the live
Stable Mainruleset. - Method: separate Standards, Spec, and Correctness passes; traced event handling, release classification, manifest trust, signer authorization, generated-release verification, status revocation, and required-check configuration.
| Behavior or check | Method / command | Result | Evidence source |
|---|---|---|---|
| Workflow regression suite | npx --yes pnpm@11.11.0 test:ci-scripts |
31 passed, 0 failed | Reviewer-run |
| PR-authored private manifest | Major core changeset plus core.private=true |
Pending 2.0.0 major reported introduced_major:false |
Reviewer-run reproduction |
| Stacked target branch | Ran the preview against hard-coded main and the immutable PR base |
true against main; correctly false against the PR base |
Reviewer-run reproduction |
| Required status | Read live ruleset 18938797 | No required_status_checks rule |
GitHub API |
| Signoff revocation | Traced context failure through the gate condition | Existing success can survive deleted approval evidence | Source trace; existing review thread agrees |
- Limits: no live workflow events or repository settings were changed. Current CI is green, but its suite does not cover the manifest bypass, stacked-branch behavior, or stale-success revocation.
- CI and bot review: all current checks pass. The existing Greptile revocation finding remains valid and is not duplicated inline.
- Event: REQUEST_CHANGES.
Written by Code Reviewer bot on behalf of Cam.
Addresses Cam's two must-fix findings on #201, porting the fixes from react #418 (which merged to journey-to-the-shadow-dom, not main, so react's main still carries both). A PR supplied its own workspace manifests, and the detector reads `private` out of them to decide what is published. A branch could mark a package private to hide its own major, then restore publication later without a new changeset. Base-owned manifests are now restored before classification. Classification also compared against main's moving tip rather than the PR's base, so a stacked PR inherited its target branch's major changeset. It now uses the merge base of the two immutable endpoints.
jhampton
left a comment
There was a problem hiding this comment.
Let's talk through the CODEOWNERS thing.
cameronapak
left a comment
There was a problem hiding this comment.
Review
Summary
Standards: 0 must-fix. Spec: 2 must-fix. Primary concern: a revoked signoff can leave a stale green status, and the status is still not required on main.
Review evidence
- Scope: reviewed revision, YPE-5850 and YPE-5849, PR discussion, all changed files, repository guidance, upstream React PR #422, and the live
Stable Mainruleset. - Method: separate Standards, Spec, and Correctness passes; traced release classification, generated-release verification, signoff revocation, status publication, and repository enforcement. Existing inline threads cover both remaining findings, so this review does not duplicate them.
| Behavior or check | Method / command | Result | Evidence source |
|---|---|---|---|
| Base-owned manifests and stacked PR classification | npx --yes pnpm@11.11.0 test:ci-scripts |
34 passed, 0 failed; both prior code findings are fixed | Reviewer-run |
| Signoff revocation after context failure | Traced context failure through the gate condition and compared upstream React PR #422 |
Existing success can survive; upstream fix is not yet ported | Reviewer source trace; open Greptile thread agrees |
| Required status | Read live ruleset 18938797 | No required_status_checks rule |
GitHub API |
| Current CI | Inspected PR checks | All checks pass | GitHub Actions |
- Limits: no live workflow event or repository setting was changed. The first local test attempt failed because the orb's pnpm wrapper cache was incomplete; rerunning with the pinned pnpm version through
npxsucceeded. Release-owner authorization is intentionally deferred to YPE-5849. - CI and bot review: CI is green. The unresolved Greptile revocation finding remains valid. When React PR #422 is ported, its tests should also assert the failure status payload, not only that a status request occurs.
- Event: REQUEST_CHANGES.
Written by Code Reviewer bot on behalf of Cam.
cameronapak
left a comment
There was a problem hiding this comment.
Review
Summary
Standards: 0 must-fix. Spec: 2 must-fix. Primary concern: a revoked signoff can leave a stale green status, and the status is still not required on main.
Review evidence
- Scope: Follow-up inline context for the review on this revision.
- Method: The comments below link the earlier discussions and identify the exact workflow locations for both remaining blockers.
- Checks: The focused suite passes 34/34 and current CI is green; source tracing still reproduces the context-failure gap, and the live
Stable Mainruleset still has no required status checks. - Limits: No live workflow event or repository setting was changed. Release-owner authorization remains deferred to YPE-5849.
- Event: REQUEST_CHANGES.
Written by Code Reviewer bot on behalf of Cam.
Ports the React SDK gate (#387, #404, #405) to this repo. Release scope is filtered on the `private` publish flag rather than membership of the changesets `fixed` group. Changesets versions private workspace packages it never publishes (apps/example), which otherwise yields a second version and breaks the one-version check. Filtering on the group instead would fail open: a publishable package added outside it would be skipped, and a major on it would report introduced_major=false. Keyed this way such a package stays in scope and trips the one-version error loudly.
Addresses Cam's two must-fix findings on #201, porting the fixes from react #418 (which merged to journey-to-the-shadow-dom, not main, so react's main still carries both). A PR supplied its own workspace manifests, and the detector reads `private` out of them to decide what is published. A branch could mark a package private to hide its own major, then restore publication later without a new changeset. Base-owned manifests are now restored before classification. Classification also compared against main's moving tip rather than the PR's base, so a stacked PR inherited its target branch's major changeset. It now uses the merge base of the two immutable endpoints.
529dcae to
a77f904
Compare
cameronapak
left a comment
There was a problem hiding this comment.
Review
Summary
Standards: 6 must-fix. Spec: 1 must-fix. Primary concern: unsigned major releases can still receive or retain the required success status.
Review evidence
- Scope: reviewed revision, YPE-5850 and YPE-5849, all changed files, repository guidance, existing review threads, upstream React PRs #416 and #422, GitHub Actions/status documentation, and the live
Stable Mainruleset. - Method: separate Standards, Spec, and Correctness passes; Oracle review reconciled against direct source inspection and focused reproductions. Previously fixed manifest restoration, merge-base classification, and context-failure fallback were reverified and are not repeated.
| Behavior or check | Method / command | Result | Evidence source |
|---|---|---|---|
| Workflow regression suite | npx --yes pnpm@11.11.0 test:ci-scripts |
40 passed, 0 failed | Reviewer-run |
| Changesets input parity | Real changeset status with breaking-README.md and a regular-file-to-symlink type change |
Both computed a major while the detector reported introduced_major:false |
Reviewer-run reproductions |
| Detector package scope | Added scripts/package.json named @changesets/parse with a PR-owned export |
Trusted detector loaded the PR parser and reported introduced_major:false for a real major |
Reviewer-run reproduction |
| Status identity and cancellation | Traced SHA/context writes and always() against GitHub's status and cancellation documentation |
Shared heads, retargets, and superseded runs can preserve or overwrite success | Reviewer source trace / GitHub Docs |
| Required status | Read live ruleset 18938797 immediately before submission | No required_status_checks rule; existing Spec blocker remains |
GitHub API |
| Current HEAD and CI | Rechecked PR head and checks | HEAD unchanged; all current checks green | GitHub API |
- Limits: no live race, workflow outage, or repository setting change was induced. Release-owner authorization remains deferred to YPE-5849 as clarified in the existing discussion.
- CI and bot review: CI and Greptile are green, but the focused suite does not exercise the reproduced parser inputs, status publication sequence, or cancellation/shared-head behavior. The existing required-status thread covers the Spec blocker and is not duplicated inline.
- Event: REQUEST_CHANGES.
Written by Code Reviewer bot on behalf of Cam.
… failures scoped Claude-Session: https://claude.ai/code/session_01Gnt93sVjs6ApXSzkDC2efU
|
All six addressed except one. Greptile then found two more on top, both fixed.
Greptile's two: renaming an ignored changeset ( Not done: the PR-authored writer (:368). A 46 tests, every fix mutation-checked. |
cameronapak
left a comment
There was a problem hiding this comment.
Review
Summary
Standards: 1 must-fix. Spec: 2 must-fix. Primary concern: a major can still be classified or authorized without this PR's signoff.
- New Spec finding: Changesets still accepts legacy nested changeset directories, but the detector drops them.
- Existing Standards finding: main-targeted PRs that share a head still share one authoritative status, and retarget reevaluation does not invalidate an old success before the terminal write.
- Existing Spec finding: the live
Stable Mainruleset still does not requiremajor-release-signoff.
Review evidence
- Scope: reviewed revision, YPE-5850 and YPE-6015, all changed files, current and resolved review threads, the pinned Changesets reader, upstream React implementation, and the live
Stable Mainruleset. - Method: separate Standards, Spec, and Correctness passes; Oracle and Librarian reports reconciled against direct source inspection, current discussion, and focused reproduction.
| Behavior or check | Method / command | Result | Evidence source |
|---|---|---|---|
| Workflow regression suite | CI=true npx --yes pnpm@11.11.0 test:ci-scripts |
46 passed, 0 failed | Reviewer-run |
| Real-reader parity test | node --test scripts/preview-release.test.mjs |
2 passed, but this file is not invoked by test:ci-scripts or CI |
Reviewer-run / source trace |
| Legacy Changesets input | Added .changeset/legacy-major/{changes.md,changes.json} with a core major and ran the preview against the two immutable commits |
Changesets computed 2.0.0 major while the gate returned introduced_major:false and no added changesets |
Reviewer-run reproduction |
| Shared status identity | Traced two main-targeted PRs sharing one head and a retarget with an earlier unsuffixed success | Both remain keyed by the same head SHA and major-release-signoff; the existing thread remains applicable |
Reviewer source trace |
| Required status | Read live ruleset 18938797 immediately before submission | No required_status_checks rule |
GitHub API |
| Current HEAD and CI | Rechecked PR head and checks | HEAD unchanged; all current checks green | GitHub API |
- Limits: no live race, workflow outage, or repository setting change was induced. The first local suite attempt hit the orb's package-manager wrapper/TTY state; the pinned CI-mode rerun passed.
- CI and bot review: CI is green. Greptile's unresolved fallback finding is a fail-closed availability tradeoff, not an unsigned-release bypass. The PR-authored writer is intentionally deferred to YPE-6015 and is not counted here, provided this status is not enforced before that trusted controller lands. Release-owner authorization remains deferred to YPE-5849.
- Event: REQUEST_CHANGES.
Written by Code Reviewer bot on behalf of Cam.
…dirs included Claude-Session: https://claude.ai/code/session_01Gnt93sVjs6ApXSzkDC2efU
cameronapak
left a comment
There was a problem hiding this comment.
Review
Summary
Standards: 1 must-fix. Spec: 2 must-fix. Primary concern: accepted legacy Changesets inputs can still produce a major while the detector reports introduced_major:false.
- New Spec finding: compare legacy changesets as complete directory inputs with Changesets' highest-bump semantics.
- Existing Standards finding: main-targeted PRs that share a head still share one authoritative status, and retarget reevaluation does not invalidate an old success before the terminal write.
- Existing Spec finding: the live
Stable Mainruleset still does not requiremajor-release-signoff.
Review evidence
- Scope: reviewed revision, YPE-5850 and YPE-6015, the full PR and changed-since delta, every current and resolved review thread, the pinned Changesets reader/planner, and the live
Stable Mainruleset. - Method: separate Standards, Spec, and Correctness passes; author replies and resolved Greptile findings reconciled against direct source inspection and focused reproductions.
| Behavior or check | Method / command | Result | Evidence source |
|---|---|---|---|
| CI script regression suites | CI=true npx --yes pnpm@11.11.0 test:ci-scripts |
48 shell checks and 4 Node tests passed | Reviewer-run |
| Duplicate legacy levels | Added one legacy JSON array containing core major followed by core minor |
Changesets computed 2.0.0 major while the detector returned introduced_major:false |
Reviewer-run reproduction |
| Completing a partial legacy input | Base contained major changes.json; head added the missing changes.md |
Changesets computed 2.0.0 major while the detector returned introduced_major:false |
Reviewer-run reproduction |
| Shared status identity | Traced two main-targeted PRs sharing one head and a retarget with an earlier unsuffixed success | Both remain keyed by the same head SHA and major-release-signoff; the existing thread remains applicable |
Reviewer source trace |
| Required status | Read live ruleset 18938797 immediately before submission | No required_status_checks rule |
GitHub API |
| Current HEAD and CI | Rechecked PR head and checks | HEAD unchanged; all current checks green | GitHub API |
- Limits: no live race, workflow outage, or repository setting change was induced.
- CI and bot review: CI is green. The resolved dot-directory and minor-overblocking findings are fixed. The PR-authored writer remains deferred to YPE-6015 and is not counted here, provided this status is not enforced before that trusted controller lands. Release-owner authorization remains deferred to YPE-5849.
- Event: REQUEST_CHANGES.
Written by Code Reviewer bot on behalf of Cam.
cameronapak
left a comment
There was a problem hiding this comment.
Review
Summary
Standards: 1 must-fix. Spec: 2 must-fix. Primary concern: the valid none release level can still hide a later major from the signoff detector.
- New Spec finding: include
nonein release precedence and fail closed on unsupported legacy levels. - Existing Standards finding: main-targeted PRs that share a head still share one authoritative status, and retarget reevaluation does not invalidate an old success before the terminal write.
- Existing Spec finding: the live
Stable Mainruleset still does not requiremajor-release-signoff.
Review evidence
- Scope: reviewed revision, YPE-5850 and YPE-6015, the full PR and changed-since delta, every current and resolved review thread, the pinned Changesets reader/planner, and the live
Stable Mainruleset. - Method: separate Standards, Spec, and Correctness passes; author replies and resolved Greptile findings reconciled against direct source inspection and focused reproduction.
| Behavior or check | Method / command | Result | Evidence source |
|---|---|---|---|
| CI script regression suites | CI=true npx --yes pnpm@11.11.0 test:ci-scripts |
51 shell checks and 4 Node tests passed | Reviewer-run |
Valid none followed by major |
Added one legacy array containing core none followed by core major |
Changesets computed 2.0.0 major while the detector returned introduced_major:false |
Reviewer-run reproduction |
| Legacy directory rename | Traced the old/new directory comparison and ran the focused suite | 4e80e4e follows the old path and keeps a pure rename green; Greptile's finding is fixed |
Reviewer source trace / regression fixture |
| Shared status identity | Traced two main-targeted PRs sharing one head and a retarget with an earlier unsuffixed success | Both remain keyed by the same head SHA and major-release-signoff; the existing thread remains applicable |
Reviewer source trace |
| Required status | Read live ruleset 18938797 immediately before submission | No required_status_checks rule |
GitHub API |
| Current HEAD and CI | Rechecked PR head and checks | HEAD unchanged; all current checks green | GitHub API |
- Limits: no live race, workflow outage, or repository setting change was induced.
- CI and bot review: CI is green. The resolved duplicate-level, partial-directory, and rename findings are fixed for their covered cases. The PR-authored writer remains deferred to YPE-6015 and is not counted here, provided this status is not enforced before that trusted controller lands. Release-owner authorization remains deferred to YPE-5849.
- Event: REQUEST_CHANGES.
Written by Code Reviewer bot on behalf of Cam.
There was a problem hiding this comment.
Review
Summary
Standards: 1 must-fix. Spec: 1 must-fix. Primary concern: the remaining required-status lifecycle and repository enforcement gaps.
The none precedence finding is fixed. Two previously reported blockers remain:
- Standards: Retarget lifecycle - after a PR is retargeted to
main, its earlier unsuffixed success remains authoritative while reevaluation runs. If this status is required, that stale green window can authorize a merge before the new result is published. - Spec: Repository enforcement - the live
Stable Mainruleset does not requiremajor-release-signoff, so the gate currently blocks no merge.
The existing threads contain the implementation detail, so this review does not duplicate them inline.
Review evidence
- Scope: reviewed revision, YPE-5850 and YPE-6015, the full PR and changed-since delta, all current and resolved review threads, and the live
Stable Mainruleset. - Method: source paths traced, latest author response verified, focused regression suite run, and remaining findings reconciled against their existing threads.
| Behavior or check | Method / command | Result | Evidence source |
|---|---|---|---|
| Changesets release precedence | CI=true npx --yes pnpm@11.11.0 test:ci-scripts |
54 shell checks and 4 Node tests passed; none ranks below patch, lone none stays green, and unsupported levels fail closed |
Reviewer-run |
| Retarget status lifecycle | Traced the main/feature contexts and pull_request.edited reevaluation |
An earlier unsuffixed success remains authoritative while the retarget reevaluation runs | Reviewer source trace; existing open thread |
| Required status | Read live ruleset 18938797 immediately before submission | No required_status_checks rule |
GitHub API |
| Current HEAD and CI | Rechecked PR head and checks | HEAD unchanged; all current checks green | GitHub API |
- Limits: no live race or repository setting mutation was induced. The trusted default-branch controller is deferred to YPE-6015, and release-owner narrowing remains deferred to YPE-5849.
- CI and bot review: CI and Greptile are green. This PR still says it closes YPE-5850, whose acceptance requires an enforced gate; if the PR is explicitly rescoped to advisory staged infrastructure, the two remaining items become rollout blockers rather than blockers to this merge.
- Event: REQUEST_CHANGES.
Written by Code Reviewer bot on behalf of Cam.
…ot skip preview Claude-Session: https://claude.ai/code/session_01Gnt93sVjs6ApXSzkDC2efU
cameronapak
left a comment
There was a problem hiding this comment.
Review
Summary
Standards: 0 must-fix. Spec: 0 must-fix. Primary concern: none.
The retarget ordering issue is fixed. This PR is approved as advisory infrastructure; YPE-6015 owns the trusted controller, and YPE-6017 owns repository enforcement after that controller is verified.
Review evidence
- Scope: reviewed revision, YPE-5850, YPE-6015, and YPE-6017, the full PR and changed-since delta, and all current and resolved review threads.
- Method: traced the retarget and ordinary-event dependency paths, verified the accepted staged scope, ran the focused suites, and checked live workflow runs and repository rules.
| Behavior or check | Method / command | Result | Evidence source |
|---|---|---|---|
| Workflow regression suites | CI=true npx --yes pnpm@11.11.0 test:ci-scripts |
58 shell checks and 4 Node tests passed | Reviewer-run |
| Normal synchronize lifecycle | Run 36624158847 | Context, preview, and gate all succeeded | GitHub Actions |
| Retarget ordering | Source trace and structural assertions | Pending write is the first conditional context step; no skipped cross-job dependency remains |
Reviewer source trace / focused suite |
| Current HEAD and CI | Rechecked immediately before submission | HEAD unchanged; CI, signoff, and Greptile green | GitHub API |
- Limits: no live retarget race or repository setting mutation was induced. Enforcement remains intentionally disabled.
- CI and bot review: all current checks pass. Do not require
major-release-signoffuntil YPE-6015's trusted controller is merged and verified; YPE-6017 tracks the later admin rollout. - Event: APPROVE.
Written by Code Reviewer bot on behalf of Cam.
Part of YPE-5850. Ports the React SDK's breaking-change signoff gate to this repo.
A PR that adds a changeset declaring a
majorbump cannot merge until a collaborator with write access comments with four things: the verbatim acknowledgment phrase, the next version, the full 40-character head SHA, and a 🚀. Everything the gate cannot evaluate fails closed.Scope: the gate lands here, enforcement follows
This PR is deliberately not the whole of YPE-5850. It adds the workflow, the detector and the test suite; it does not make the status required.
Stable Mainhas norequired_status_checksrule, somajor-release-signoffis advisory until an administrator adds it. That is a repository setting, not something a PR can carry. Requiring it before this merges would also strand every PR on a check that does not exist yet, so the order has to be this way round.Two follow-ups, neither blocking this merge:
major-release-signofftoStable Mainas a required status check, by context name rather than job name. Needs an admin.pull_request, so its ownrunbodies come from the PR. Moving evaluation and publication to a default-branchworkflow_runcontroller is a merge-then-verify change and cannot be exercised by the PR containing it. Same change is needed in React and Swift.Release-owner narrowing stays deferred to YPE-5849.
What was ported
React's workflow as of #404 and #405, not the original. Those two matter:
changeset-botcomment can no longer cancel the in-flight evaluation and paint the PR red. It also separatesblockedfromis_major, so an unevaluable preview reports "unable to determine" rather than falsely claiming a breaking change.changeset status. Rather than allowlisting file paths, it checks out the immutable base, runs base-ownedpnpm version-packages, and compares complete git trees. The gate clears only when both the job succeeded and the trees matched.Adaptations for this repo
node-version: 24→node-version-file: 'package.json', sinceengines.nodealready declares it@changesets/parse0.4.3, what this lockfile already resolved (react pins0.4.1)Deliberately not using
./.github/actions/setup. That composite runspnpm install --frozen-lockfilewith lifecycle scripts and pnpmfile enabled. The hardened--ignore-scripts --ignore-pnpmfileinstall was a security fix on the react original, and reusing the composite in the job that handles PR-authored code would reopen it.One real difference this surfaced
This workspace includes
apps/*, andapps/exampleisprivate: true. Changesets still versions private packages, it just never publishes them, sochangeset statusreturned two different next versions (1.7.0 and 1.0.10) and the script exited 1.scripts/preview-release.mjsnow scopes the release set to packages that are actually published, keyed on theprivateflag via@manypkg/get-packages.Worth stating why it is keyed that way, because the obvious alternative is wrong. Filtering on membership of the changesets
fixedgroup fails open: the group is a versioning policy, not a publish flag, so a publishable package added outside it (exactly how react hashooks) would be skipped, and amajoron it would reportintroduced_major: falseand post a green status on a breaking release. I wrote that version first and an adversarial review caught it. Keyed onprivate, such a package stays in scope and trips the one-version error loudly instead.React has the same latent bug. It survives only because its example has no pending bump. Worth a follow-up there.
Also
pnpm test:ci-scriptsis wired into thelintjob. React never runs this suite in CI, so porting the 296-line test script without wiring it would have shipped a test nothing executes.Verified locally
31/31 signoff tests;
preview-release.mjsrun against real history and against synthesized major changesets in a throwaway worktree, confirming a major on a published package gates (2.0.0,introduced_major: true), a major on the private example does not, and a publishable package outside the fixed group now fails closed; prettier clean; both workflow files parse; lockfile resolves the same 1,715 packages as before, with no version changes.Needed from an admin before this gates anything
major-release-signoffis not in this repo'sStable Mainruleset. I checked:required_status_checksis empty. Until it is registered, the workflow runs and posts its status but no merge is blocked. Same gap that left the react gate inert until it was added there on 15 September.The changes since the previous review appear safe to merge; the signoff status remains advisory until the planned repository-setting follow-up.
Summary
The PR adds an advisory major-release signoff workflow, release-impact detector, and CI regression tests. Since the previous review, it moves the retarget pending-status write into the context job so it precedes evaluation without a skipped-job dependency.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR E[PR or comment event] --> C[Resolve PR context] E -- PR edited --> H[Mark status pending] H --> C C --> P[Compute preview or verify generated release] P --> G[Publish signoff status]Reviews (15) · Last reviewed commit: "fix(ci): hold the status inside the cont..."