Follows the rule-5 change in mathias/local-dev (f30dbf6): the PR trigger is now risk, not concurrency.risk/low commits straight to main; risk/medium+ opens a PR.
tbd_ship already implements the hard half — a fail-closed CI gate, described in its own code as "the load-bearing safety of tbd_ship... it must NEVER return merge=true" without every workflow green. What it lacks is the risk tier as an input, so it currently treats all work identically.
What to add
Read the issue's risk/ label and refuse to auto-merge anything above risk/low:
Tier
tbd_ship behaviour
risk/low + all CI green
auto-merge (today's behaviour)
risk/medium, risk/high
refuse — leave the PR open for a human, and say why
no risk/ label
refuse — unclassified is not low
That last row matters most. An unlabelled issue must not inherit the permissive path: the whole taxonomy rests on classification being deliberate, and defaulting absent-to-low would make forgetting a label the way to bypass review.
Why this is the right home
The alternative is a new merge tool. tbd_ship's gate is already the audited, fail-closed piece and already has tbd_ship_internal_test.go asserting the gate "must NEVER return merge=true" under failure. Adding a predicate to a tested gate beats building a second gate that needs its own proof.
Test it the way the existing gate is tested
Per the repo's own standard, the new predicate needs a case proving it refuses when it should — not just that it merges when it should:
risk/medium + all-green CI → no merge, reason reported
no risk/ label + all-green CI → no merge
risk/low + all-green → merges (unchanged)
risk/low + any non-green → no merge (unchanged, must not regress)
Context worth carrying
The estate has merged everything on green until now, which diverges from what assessor-loop's transfer story §4.1 tells a client we do. §5 of that document concedes the reviewed lane is undemonstrated. This change is what makes it demonstrated — the estate becomes its own evidence rather than an exception to its own model.
Related
brain fail-closed-ci-gate-auto-merge-pattern — the pattern this extends
brain gitea-pr-merge-autoclose-and-review-comment-footguns — merging auto-closes gated issues; worth checking whether tbd_ship should refuse to merge a PR whose linked issue carries a hold
mathias/local-dev.context/AGENT.md rule 5 — the rule this enforces
Follows the rule-5 change in `mathias/local-dev` (`f30dbf6`): **the PR trigger is now risk, not concurrency.** `risk/low` commits straight to main; `risk/medium`+ opens a PR.
`tbd_ship` already implements the hard half — a fail-closed CI gate, described in its own code as *"the load-bearing safety of tbd_ship... it must NEVER return merge=true"* without every workflow green. What it lacks is the **risk tier as an input**, so it currently treats all work identically.
## What to add
Read the issue's `risk/` label and refuse to auto-merge anything above `risk/low`:
| Tier | `tbd_ship` behaviour |
|---|---|
| `risk/low` + all CI green | auto-merge (today's behaviour) |
| `risk/medium`, `risk/high` | **refuse** — leave the PR open for a human, and say why |
| no `risk/` label | **refuse** — unclassified is not low |
That last row matters most. An unlabelled issue must not inherit the permissive path: the whole taxonomy rests on classification being deliberate, and defaulting absent-to-low would make forgetting a label the way to bypass review.
## Why this is the right home
The alternative is a new merge tool. `tbd_ship`'s gate is already the audited, fail-closed piece and already has `tbd_ship_internal_test.go` asserting the gate *"must NEVER return merge=true"* under failure. Adding a predicate to a tested gate beats building a second gate that needs its own proof.
## Test it the way the existing gate is tested
Per the repo's own standard, the new predicate needs a case proving it **refuses** when it should — not just that it merges when it should:
- `risk/medium` + all-green CI → **no merge**, reason reported
- no `risk/` label + all-green CI → **no merge**
- `risk/low` + all-green → merges (unchanged)
- `risk/low` + any non-green → no merge (unchanged, must not regress)
## Context worth carrying
The estate has merged everything on green until now, which diverges from what `assessor-loop`'s transfer story §4.1 tells a client we do. §5 of that document concedes the reviewed lane is **undemonstrated**. This change is what makes it demonstrated — the estate becomes its own evidence rather than an exception to its own model.
## Related
- brain `fail-closed-ci-gate-auto-merge-pattern` — the pattern this extends
- brain `gitea-pr-merge-autoclose-and-review-comment-footguns` — merging auto-closes gated issues; worth checking whether `tbd_ship` should refuse to merge a PR whose linked issue carries a hold
- `mathias/local-dev` `.context/AGENT.md` rule 5 — the rule this enforces
Done and live 2026-10-08 (PR #73, squash d3faffc, tag v0.17.0). Mathias authorized the merge and deploy.
Reviewed: adversarial pass by execution (PASS, no bypass; 14 reviewer mutants, 12 killed, survivors addressed); task check green; CI green on the PR head (run 9693).
Deployed: CD pinned localhost:5000/gitea-mcp:d3faffc in infra; rollout 1/1 ready, 0 restarts. Rollback target was dbf78a3.
Verified live through the production MCP:tools/list for tbd_ship went from no issue input / no risk/low in the description (before) to both present (after). Live-fire on a throwaway PR (closed, branch deleted, nothing on main): issue #63 (risk/medium) with CI green and polled to completion → merged:false, ci_status:success, risk:medium, reason "above risk/low a human reviews the change"; no issue named → merged:false, risk:none, "unclassified is not low". Also exercised the real GetIssue response shape.
Not live-tested: the positive case (a risk/low issue merging on green) — it would merge a throwaway file onto main; covered by the unit and end-to-end tests.
Limits, tracked: the caller names the issue (trusted); pr_merge is a separate ungated merge path → #74.
Behaviour change: a tbd_ship call without issue now opens the PR and stops.
Done and live 2026-10-08 (PR #73, squash d3faffc, tag v0.17.0). Mathias authorized the merge and deploy.
- **Reviewed:** adversarial pass by execution (PASS, no bypass; 14 reviewer mutants, 12 killed, survivors addressed); `task check` green; CI green on the PR head (run 9693).
- **Deployed:** CD pinned `localhost:5000/gitea-mcp:d3faffc` in infra; rollout 1/1 ready, 0 restarts. Rollback target was `dbf78a3`.
- **Verified live through the production MCP:** `tools/list` for `tbd_ship` went from no `issue` input / no `risk/low` in the description (before) to both present (after). Live-fire on a throwaway PR (closed, branch deleted, nothing on main): issue #63 (`risk/medium`) with CI green and polled to completion → `merged:false`, `ci_status:success`, `risk:medium`, reason "above risk/low a human reviews the change"; no issue named → `merged:false`, `risk:none`, "unclassified is not low". Also exercised the real `GetIssue` response shape.
- **Not live-tested:** the positive case (a `risk/low` issue merging on green) — it would merge a throwaway file onto main; covered by the unit and end-to-end tests.
- **Limits, tracked:** the caller names the issue (trusted); `pr_merge` is a separate ungated merge path → #74.
- **Behaviour change:** a `tbd_ship` call without `issue` now opens the PR and stops.
---
_Created via git-mcp on behalf of @mathias_
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Follows the rule-5 change in
mathias/local-dev(f30dbf6): the PR trigger is now risk, not concurrency.risk/lowcommits straight to main;risk/medium+ opens a PR.tbd_shipalready implements the hard half — a fail-closed CI gate, described in its own code as "the load-bearing safety of tbd_ship... it must NEVER return merge=true" without every workflow green. What it lacks is the risk tier as an input, so it currently treats all work identically.What to add
Read the issue's
risk/label and refuse to auto-merge anything aboverisk/low:tbd_shipbehaviourrisk/low+ all CI greenrisk/medium,risk/highrisk/labelThat last row matters most. An unlabelled issue must not inherit the permissive path: the whole taxonomy rests on classification being deliberate, and defaulting absent-to-low would make forgetting a label the way to bypass review.
Why this is the right home
The alternative is a new merge tool.
tbd_ship's gate is already the audited, fail-closed piece and already hastbd_ship_internal_test.goasserting the gate "must NEVER return merge=true" under failure. Adding a predicate to a tested gate beats building a second gate that needs its own proof.Test it the way the existing gate is tested
Per the repo's own standard, the new predicate needs a case proving it refuses when it should — not just that it merges when it should:
risk/medium+ all-green CI → no merge, reason reportedrisk/label + all-green CI → no mergerisk/low+ all-green → merges (unchanged)risk/low+ any non-green → no merge (unchanged, must not regress)Context worth carrying
The estate has merged everything on green until now, which diverges from what
assessor-loop's transfer story §4.1 tells a client we do. §5 of that document concedes the reviewed lane is undemonstrated. This change is what makes it demonstrated — the estate becomes its own evidence rather than an exception to its own model.Related
fail-closed-ci-gate-auto-merge-pattern— the pattern this extendsgitea-pr-merge-autoclose-and-review-comment-footguns— merging auto-closes gated issues; worth checking whethertbd_shipshould refuse to merge a PR whose linked issue carries a holdmathias/local-dev.context/AGENT.mdrule 5 — the rule this enforcesDone and live 2026-10-08 (PR #73, squash
d3faffc, tag v0.17.0). Mathias authorized the merge and deploy.task checkgreen; CI green on the PR head (run 9693).localhost:5000/gitea-mcp:d3faffcin infra; rollout 1/1 ready, 0 restarts. Rollback target wasdbf78a3.tools/listfortbd_shipwent from noissueinput / norisk/lowin the description (before) to both present (after). Live-fire on a throwaway PR (closed, branch deleted, nothing on main): issue #63 (risk/medium) with CI green and polled to completion →merged:false,ci_status:success,risk:medium, reason "above risk/low a human reviews the change"; no issue named →merged:false,risk:none, "unclassified is not low". Also exercised the realGetIssueresponse shape.risk/lowissue merging on green) — it would merge a throwaway file onto main; covered by the unit and end-to-end tests.pr_mergeis a separate ungated merge path → #74.tbd_shipcall withoutissuenow opens the PR and stops.Created via git-mcp on behalf of @mathias