Green was the thing forcing the safe path (revert T-1503, harden the handoff) - #766
Conversation
Reverts 532c30f and hardens the handoff it was built on top of. T-1503 made a source-only pull request green. Green is mergeable, and the button then lands `src/` with no rebuilt `dist/` -- `main` carrying a bundle that does not match its source until the next push run goes red, after the fact rather than before it. #761 was red, and that redness is why it went through `canonical-merge` instead of through its own merge button. The friction was the mechanism. What T-1503 was written to fix is already fixed by T-1502 and without the risk: a contributor opens the source-only pull request and never rebuilds anything. Its checks being red is a statement about the artifact, not a demand on them. The measurement T-1503 produced is kept in the ticket, because it is the part worth having: "drop the diff line" was wrong, since CI rebuilds before it verifies and a source-only pull request dies at `artifact:verify`. Then the handoff, which was proven against a pull request the owner wrote and not against one a stranger wrote. `publish` said it "only moves bytes it verifies" and verified none of them. The rebuild job runs a contributor's `package.json` and every lifecycle script `npm ci` pulls in; anything there could add a commit touching `src/`, `scripts/` or `.github/`, and this job would force-push it under the App's identity for a reviewer to read as a rebuild. It now recomputes the merge itself from `main` and the pull request ref -- neither writable from the first job -- pins both parents, and allows a difference only inside `dist/` and the manifest. It also refuses a pull request that moved, not only a moved `main`: a force-push between the first job's fetch and the push would leave the canonical pull request carrying a head nobody reviewed while #N displays something else. The push is read back by sha. `git push` exiting zero says the push was accepted, not that the branch is still what this run put there. The App token is asked for narrowly rather than taken whole. A body-less mint carries every permission the installation holds on every repository it is installed on, and grows silently as the installation grows; it is now scoped to this repository with `contents: write` and `pull_requests: write`. T-1504's acceptance moves from a person-observation to the system properties it was standing in for, and the PRD's success criteria are fixed against the runs that show them. The old first criterion -- "no pull request carries `dist/`" -- is contradicted by the thing that was built: the canonical pull request carries exactly that, and carrying it is how the required contexts run on the tree that lands. Limit: merge-commit-only is still a sentence in a pull request body rather than a repository setting, so a squash remains one click away and would leave the source pull request open Blast: module Undo: easy Certainty: firm Verified: reverting the hardening fails all three new workflow assertions and restoring it passes nineteen; the ordering assertion for the credential step was matching the other job's identically-named step after a rename and was re-anchored; ci.yml's locked digest is back to 473b1fc0 and matches the file Record-Id: r-t1503revert Provenance: authored CommitLore-Version: 2.0.0
CommitLore — record lintTrailers: clean — 3 commits in Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR. |
The ticket said the method "is not enforceable from here" and handed the choice to the owner. That was wrong in a specific way: it was enforceable, it just was not enforced. `allow_squash_merge` and `allow_rebase_merge` are now off, so a merge commit is the only method GitHub offers on this repository. The canonical pull request depends on its head commit staying an ancestor, and a squash lands new bytes instead -- leaving the source pull request open with nothing to point at. Until now the only thing holding that was a sentence in the body asking for a merge commit, which is a check somebody has to read. #752 is what that costs: during the 1.1.3 release a body sentence failed in the seat next to this one, and there is no API to convert a pull request from closed to merged afterwards. What this moves rather than removes is written down too. The guarantee now lives in a repository setting, which nothing in this repository reads: a future owner can re-enable squash and no test, workflow or gate here will notice -- only a canonical pull request quietly failing to close its source. Limit: nothing in this repository can read the setting, so the only signal that it was reverted is a source pull request left open after a canonical merge Blast: local Undo: easy Certainty: firm Record-Id: r-squashoff818 Provenance: authored Verified: read back from the API after the change -- merge=true squash=false rebase=false CommitLore-Version: 2.0.0
|
Merge-commit-only is now enforced. The owner directed it and it landed; I read it back from the API rather than taking it on report: So the Limit on the first commit here is out of date, and The ticket said the method "is not enforceable from here" and handed the choice to the owner. That was wrong in a specific way: it was enforceable, it just was not enforced. The canonical pull request's body still says it, for a reader who wants to know why the branch is shaped that way. It is no longer what holds the property. What moved rather than disappeared, recorded as the new Limit: the guarantee now lives in a repository setting that nothing in this repository reads. A future owner can re-enable squash and no test, workflow or gate here will notice — only a canonical pull request quietly failing to close its source. Smaller surface than a sentence in a body, and not zero. |
The inline version required the canonical branch tip to have two parents. A real tip has one. #762's `6a88f2f4` is a rebuild commit whose parent `acfed4a5` is the two-parent merge, because the rebuild commits on top of the merge whenever `dist/` actually changes -- which is the ordinary case, not an edge one. The tests I wrote for it passed. They grep the workflow as text, and text cannot be run. So the check moves into `scripts/verify-canonical-handoff.mjs`, where the invariant is a commit graph and a graph can be built and mutated. `test/verify-canonical-handoff.test.ts` builds a repository shaped like a real run -- main with a committed bundle, a source-only branch, a `--no-ff` merge, a rebuild on top -- and applies one mutation per case: a workflow file added to the rebuild commit refused, names the path a source file added to the rebuild commit refused, names the path the merge's second parent replaced refused, names both commits an edit amended into the merge itself refused on the tree, right parents a source branch carrying `dist/` itself refused before anything else two commits stacked on the merge refused -- one rebuild, not two the two honest shapes accepted The tree check is the one the parent check cannot do. `git merge-tree --write-tree` recomputes the merge here from the two commits the caller resolved from GitHub, so a merge with the right parents and a smuggled edit does not survive having the right shape. The workflow-text test now asserts the inline form is *gone*, so it cannot return without the graph tests coming with it. Limit: the script trusts the two shas it is given, so it is only as good as the caller resolving them from GitHub rather than from the bundle Blast: module Undo: easy Certainty: firm Record-Id: r-handoffgraph Provenance: authored Verified: run against the real #762 handoff -- base ba9b0cf, source 09a1890, tip 6a88f2f -- which it accepts and which the inline version would have refused; deleting the script fails all eight graph tests and restoring it passes them CommitLore-Version: 2.0.0
Reverts
532c30f4and hardens the handoff it was built on top of.Why T-1503 is rejected rather than fixed
It made a source-only pull request green. Green is mergeable, and the button then lands
src/with no rebuiltdist/—maincarrying a bundle that does not match its source until the next push run goes red, after the fact rather than before it.#761 was red, and that redness is why it went through
canonical-mergeinstead of through its own merge button. The friction was the mechanism.What T-1503 was written to fix is already fixed by T-1502, without the risk: a contributor opens the source-only pull request and never rebuilds anything. Its checks being red is a statement about the artifact, not a demand on them.
The measurement T-1503 produced is kept in the ticket, because it is the part worth having: "drop the diff line" was wrong, since CI rebuilds before it verifies and a source-only pull request dies at
artifact:verifywithout ever reaching the diff.The handoff was proven against a pull request the owner wrote
publishcarried a comment saying it "only moves bytes it verifies", and it verified none of them.main+ the PR ref; both parents pinned; a difference allowed only insidedist/and the manifest#N's head moved since the rebuild fetched it, and if the ref no longer resolves to itgit pushexit codecontents: write+pull_requests: writeThe rebuild job runs a contributor's
package.jsonand every lifecycle scriptnpm cipulls in. Anything there could add a commit touchingsrc/,scripts/or.github/, and this job would have force-pushed it under the App's identity for a reviewer to read as a rebuild.T-1504 and the PRD
T-1504's acceptance moves from a person-observation — "a non-Linux contributor completing a source change unaided" — to the system properties it was standing in for. That gate cannot be satisfied from inside the repository, so it left the work permanently unfinished for a reason unrelated to the work. It was also indifferent to what it named: the canonical build never runs on a contributor's machine, so "non-Linux" was a proxy for "cannot run the canonical build" and the mechanism does not distinguish the two.
Field experience is recorded as a Limit instead: adoption evidence, not a correctness gate. A real-world failure reopens #719 or opens a defect issue.
The PRD's success criteria are fixed against the runs that show them. The old first criterion — "no pull request carries
dist/" — is contradicted by the thing that was built: the canonical pull request carries exactly that, and carrying it is how the required contexts run on the tree that lands.Verified
Reverting the hardening fails all three new workflow assertions; restoring it passes nineteen.
The ordering assertion for the credential step was matching the other job's identically-named step after a rename — both jobs check that
mainstood still — so it was asserting nothing about the publishing job. Re-anchored on the renamed step and extended to cover the new verification.ci.yml's locked digest is back to473b1fc0…and matches the file.Still open, and it needs the owner
Merge-commit-only is not enforced. It is a sentence in a pull request body, which is a check somebody has to read — the exact shape that cost #752 its
mergedlabel during the 1.1.3 release. The repository still allows squash and rebase, and the merge button remembers the last method used.Recorded as the Limit on this commit until it is done.