Repository navigation
Land on green, and delete an unbacked live-gate assertion instead of adding observation (#241) - #242
Conversation
Mergify now merges on verify and live-gate. Only a PR that touches .github/ or the Mergify config also waits for Unfret, and a workflow resolves the threads Unfret starts so only other reviewers' threads hold a merge. REVIEW.md answers an unbacked live-gate assertion by deleting it and treats a behavior no recipe checks as an advisory. AGENTS.md carries the live-proof scope and the release rule. Closes #241 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live evidence: Open Pstack
|
Merge Protections🔴 1 of 3 protections blocking · waiting on 🙋 you
🔴 🚦 Auto-queueWaiting for
This rule is failing.When all merge protections are satisfied and these conditions match, this pull request will be queued automatically.
Show 2 satisfied protections🟢 Queue ready pull requestsAutomatically queue non-draft pull requests targeting main after verify and live-gate pass on the head and every review thread is resolved.
🟢 Unfret on risky pathsPull requests that touch .github/ or the Mergify config also wait for Unfret.
|
✅ Accepted riskAccepted by ericlitman: #241 (authority 48fa77b) 🔴 High · New resolver workflow is classified nonRuntime, so live-gate passes without exercising it 🟡 Medium · Fork PRs still get a read-only token, so Unfret threads are never resolved DetailsRun: @unfret review · @unfret status · @unfret help |
There was a problem hiding this comment.
🛑 3 blocking findings
🔴 Critical · The hold gate trusts unauthorized label removal
🔴 High · Executable workflow is excluded from live verification
🟡 Medium · Use a writable execution context for fork PR threads
Check
|
[High impact] The PR appears safe to merge under the stated land-on-green policy. SummaryThis PR changes the repository to land ordinary PRs after
Reviews (1) · Last reviewed commit: "ci, docs: land on green and delete unbac..." · Reviewed by Greptile |
The Actions token needs contents: write for resolveReviewThread: the workflow's run on 7791942 failed FORBIDDEN under pull-requests: write alone, and a probe resolved an Unfret thread once contents: write was added. Mergify cannot tell who removed a hold label, so the pause is a draft pull request instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Live evidence: Open Pstack
|
There was a problem hiding this comment.
🛑 2 blocking findings
🔴 High · New resolver workflow is classified nonRuntime, so live-gate passes without exercising it
🟡 Medium · Fork PRs still get a read-only token, so Unfret threads are never resolved
Check
|
Operator waiver for head 986c49a (#241). No open blocking finding is material. Both fail closed: each failure holds a merge until someone resolves a thread by hand, and none merges anything wrongly.
No consequence is security, secret exposure, data loss, or a gate or waiver that publishes without its run. |
|
@Mergifyio queue |
|
@Mergifyio refresh |
Merge Queue Status
This pull request spent 22 seconds in the queue, including 2 seconds running CI. Required conditions to merge
|
✅ Pull request refreshed |
Summary
Open Pstack lands on green, and an unbacked live-gate assertion is answered by deleting it.
REVIEW.md: an assertion the live gate publishes that can pass without its behavior is material, and its correction is deletion; a behavior no recipe asserts is an advisory coverage gap. The verifier entry point must back every assertion it publishes with retained evidence. A new Usage-facts contract states the merge policy.AGENTS.md: the live-proof scope (from mastra-pilot run-these),Merge policy: land on green, the merge rule, and the release rule. A draft pull request is the pause: Mergify reads noholdlabel here, because no GitHub rule limits who can remove a label.UPSTREAM.md: deletes thekeep-settingsentence from the setup-pstack: on a rerun with a valid sheet and passing probes, steps 4/6/7 should take the sheet as their answers #46 entry and step 6's recipe-update sentence; step 9 carries the release rule.CHANGES.md:3: the merge description..github/workflows/unfret-threads.yml: on each review Unfret submits, resolves every unresolved thread Unfret started. It needscontents: writeas well aspull-requests: write, because GitHub refusesresolveReviewThreadto the Actions token without it..claude/skills/verify-open-pstack/features/registry.json: maps the new workflownonRuntime, because unknown paths abort the live gate and no Claude Code or Codex surface runs a GitHub workflow. Its live test is its own run on this PR. No recipe changes.After this PR merges, this session swaps the "Review gate" ruleset (24286952) from requiring
Unfret(App 5142537) to requiringMergify Merge Protections(App 10562), the standard forericlitmanrepositories. Until then the ruleset still requiresUnfret, so this PR, and anything that merges before the swap, waits for it.Design decisions and rejected alternatives are in #241's "Current scope" block.
Evidence
Mergify cannot tell thread authors apart. Mergify's simulator (
POST /v1/repos/ericlitman/open-pstack/pulls/<n>/simulator) on PRs 98, 114 and 145, which have unresolved Unfret or Greptile threads:review-threads-unresolved ~= ^PRRT_is true andreview-threads-unresolved ~= unfret-findingis false on all three. The attribute holds thread IDs, so a workflow resolves Unfret's threads instead.The new config validates and evaluates as intended (simulator with this PR's
.mergify.yml, byte-identical to the committed file):files ~=: true on chore(tooling): delete comments from the verifier, tests and repository config (#218) #220 (.github/workflows/windows-hooks.yml,.mergify.yml) and build: add root check scripts so Unfret registers and shares open-pstack's checks (#185) #186 (.github/workflows/ci.yml); false on fix(pstack): let Claude Code subagents wait in the foreground (#232, #233) #239 and feat(pstack): sync Cursor pstack 0.15.29 as Open Pstack 1.16.0 (#234) #240.check-success = @unfret-eal/Unfret: true on feat(pstack): sync Cursor pstack 0.15.29 as Open Pstack 1.16.0 (#234) #240.@unfret/Unfretand@github-actions/Unfret: false. The App qualifier is what keeps a same-named check from another App out once the ruleset stops bindingUnfret.merge_protectionsis rejected, so the simulator validates that section.The workflow's thread query (
REPOSITORY=ericlitman/open-pstack PR=98, the committed script's query and filter, read-only) returns exactly the two unresolved Unfret threads on release: sync Cursor pstack 0.15.5 as Open Pstack 1.5.0 #98 and neither of its two Greptile threads.Thread workflow, head
7791942c: Unfret's review triggered it from this PR's own copy (run 39621500336), and it failed:resolveReviewThreadreturned FORBIDDEN ("Resource not accessible by integration") underpull-requests: write.Token probe: a push-triggered workflow on a throwaway branch (deleted after), run 39622083381, tried the mutation on two old Unfret threads of merged fix(verify): accept only the resolver launcher and equalize the Reflect fixture openers (#152) #164. With
pull-requests: writealone it returned FORBIDDEN; withcontents: writeadded it returned"isResolved":true. Head986c49aaaddscontents: write.Thread workflow, head
986c49aa: pending; Unfret's review of this head triggers it. This run is the workflow's live test.Live gate: passed on
7791942c(evidence); passed on986c49aa(evidence).verify-open-pstackran from this candidate head with--feature claude:project-skill --feature codex:project-skill --self-test, becauseregistry.jsonis a verifier file andAGENTS.mdandregistry.jsonreach onlyproject-skill; the other paths arenonRuntime. Both sessions passed 4 machine-checked assertions each.Ruleset 24286952 before the swap:
{"name":"Review gate","enforcement":"active","bypass_actors":[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"pull_request"}],"conditions":{"ref_name":{"exclude":[],"include":["~DEFAULT_BRANCH"]}},"rules":[{"type":"required_status_checks","parameters":{"strict_required_status_checks_policy":false,"do_not_enforce_on_create":true,"required_status_checks":[{"context":"Unfret","integration_id":5142537}]}}]}Ruleset 24286952 after the swap (applied 2026-10-21 15:42 UTC, right after the merge, so no PR merged without
Unfretbefore the new.mergify.ymlwas onmain).main's required checks are nowverify,live-gateandMergify Merge Protections:{"name":"Review gate","enforcement":"active","bypass_actors":[{"actor_id":5,"actor_type":"RepositoryRole","bypass_mode":"pull_request"}],"conditions":{"ref_name":{"exclude":[],"include":["~DEFAULT_BRANCH"]}},"rules":[{"type":"required_status_checks","parameters":{"strict_required_status_checks_policy":false,"do_not_enforce_on_create":true,"required_status_checks":[{"context":"Mergify Merge Protections","integration_id":10562}]}}]}Merge Danger
Door: two-way. Reverting this PR and restoring the ruleset JSON above returns the old rule.
Blast Radius: merge policy.
After the ruleset swap, a PR that touches no risky path merges before Unfret's verdict. Users install from
main, so a material defect reaches fresh installs before its fix-forward PR; the release rule delays only the next version bump. Every Unfret thread on every PR is resolved as Unfret posts it. Unfret's check still gates risky-path PRs, and its findings stay in the check and the review service. A PR that touches.github/**still needs an operator to queue it.Acceptance criteria
main."mainat merge.verifyandlive-gatewithout waiting forUnfret."Gate results (head
986c49aa)bun run testbun run typecheckbun run staticclaude plugin validate plugins/pstackno-comments: the one comment, on the workflow'scontents: write, states GitHub behavior we cannot change and stays. No constraint is left unencoded.Unfret findings on head
7791942cv1:2ec9ef2e…the hold gate trusts unauthorized label removalverdict-changing986c49aa: no GitHub rule limits who removes a label, and #241 does not requirehold.v1:9ac6ba0b…fork PR tokens cannot resolve threadsfails-loudlyv1:d191256f…the workflow is excluded from live verificationverdict-changing986c49aa: the resolver failed whilelive-gatepassed. Its live test is its own run on the PR. The finding's claim that apull_request_reviewworkflow must already be onmainis false: run 39621500336 ran this PR's copy.Not verified
986c49aaproves the workflow.Follow-up filed: MASTRA-1134. babysit-pr's risky-path hold check matches only
@unfret/Unfret, sobabysit-pr waiverefuses on an open-pstack risky-path PR until it also matches@unfret-eal/Unfret.Closes #241
🤖 Generated with Claude Code