Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
297f2e5
[US-219] fix: front-load review contract coverage
rucka Sep 1, 2026
fa4fc10
[US-219] fix: prove external boundaries
rucka Sep 1, 2026
8c5678d
[#219] fix: the review severity floor defaults to Minor
rucka Sep 3, 2026
57e4761
[#413] feat: CI runs format:check — a dedicated workflow guarded by a…
rucka Sep 3, 2026
b801ff1
[#218] docs: ADR-018 amendment — adopt Option 4 (dedicated review ide…
rucka Aug 28, 2026
8c59c64
[#218] feat: dedicated review identity + adoption-gated light auto-ap…
rucka Aug 28, 2026
016c41d
[#218] fix: make the identity exclusion + the APPROVE gate mechanical…
rucka Aug 28, 2026
ccccbdd
[#218] fix: accept the adoption kind literal + close the doc contradi…
rucka Aug 28, 2026
592584b
[#218] fix: session mode keeps the native verdict; synthesis + kind e…
rucka Aug 28, 2026
28ec952
[#218] fix: adoption read HALTs on unparseable kind; audit survives t…
rucka Aug 28, 2026
606a6e2
[#218] fix: Light row covers the blocking case; ADR states the six en…
rucka Aug 28, 2026
2be2763
[#218] fix: healthy gets a runtime source; presence anchored to the k…
rucka Aug 28, 2026
4b20d9b
[#218] fix: Review row enumerates NOT SUBMITTED; presence probe line-…
rucka Aug 29, 2026
044ba55
[#218] fix: Check row enumerates NOT PUBLISHED; AUTH probe tests memb…
rucka Aug 29, 2026
7e5bbe8
[#218] fix: the App self-authorship gate compares the shape gh emits;…
rucka Aug 29, 2026
c4a498c
[#218] fix: the supersede exit needs `statuses` in the MINT payload; …
rucka Aug 29, 2026
1a06ba6
[#218] fix: health reads the repo VARIABLE; whole-field light match; …
rucka Aug 29, 2026
be3e7ae
[#218] fix: the light match is exact on every shape; the HALT binds a…
rucka Aug 29, 2026
f1c8b85
[#218] fix: the agent exports the login clause's variable; unknown au…
rucka Aug 29, 2026
8b11eb6
[#218] fix: the audit comment follows the review by choice; its resid…
rucka Aug 29, 2026
ed8586f
[#218] fix: the generator names the login env thread; the authorship …
rucka Aug 29, 2026
613f7fa
[US-218] fix: bind review identity to gate login
rucka Aug 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions .claude/skills/pair-capability-publish-pr/SKILL.md

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .claude/skills/pair-capability-setup-gates/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ The gate is only half the merge block. The judgment review must be **unskippable
3. **Act — prerequisites first (ordering is load-bearing)**: before writing any protection, (a) **provision the `pr-state:*` labels** from the host guide (they never auto-create; absence is non-blocking but the view is lost), (b) **write the `pair-explicit-approval` job** into the repository's workflows on the **target branch** — neither needs admin scope — and (c) **observe both contexts reporting on a real PR, twice**: at PR open, and again after a review submission (the approval-time re-evaluation), each time **on the PR's head commit** — that is the only commit the host's protection reads. Requiring a context that has never reported leaves **every** PR permanently unmergeable, and a context that reports only at PR open leaves a 🔴 PR blocked *after* the human approves; so protection is applied last, with the host's admin-bypass setting (`enforce_admins` on GitHub) enabled only after one PR has merged through the new rule.
4. **Act — required checks**: add both contexts to the protected branch's required status checks, alongside the gate jobs from Step 4:
- **`pair-review`** — published by `/pair-process-review` on the head commit (pending at PR creation via `/pair-capability-publish-pr`). Required in **both** pipeline modes and at **every** tier: it is never tier-scoped away, exactly like `secret-scan`. A `pending`/absent check blocks the merge, so a review that never ran or crashed cannot yield a mergeable PR (R5.7).
- **`pair-explicit-approval`** — verifies a **non-author human** approval on the current head when the tier requires it (🔴, and untagged ⇒ 🔴 fail-safe); auto-passes at 🟢/🟡 (D10). Generate its job from the host guide's template **unmodified in three respects**, because all three are authorization properties rather than style: it must run from a **trusted ref** (the target branch's version of the job and of any projection it sources — never the pull request's own tree, or the change under review could rewrite the check that authorizes it); its verdict must be **pinned to the PR head commit** on every re-evaluation (including the approval-triggered one), since that is the commit protection evaluates; and it must publish a **pending result as its first step**, so a cancelled or aborted re-evaluation leaves a merge-blocking context instead of a stale `success` from a lower tier. When registering it, **pin the context's producer** where the host allows it (on GitHub: the `checks[].app_id` form, not the legacy `contexts` array) — an unpinned status context can be satisfied by any principal with push access, including the reviewing agent, which would void the 🔴 gate. `pair-review` stays unpinned by construction (no app publishes it): it is an anti-accident control, not an authorization one — see the host guide's "What each context proves". It reads the `risk:*` label only — no classification criteria (D18). **Single-maintainer repositories**: the host rejects a self-approval, so 🔴 PRs cannot satisfy this context without a second human account — say so explicitly and offer to leave the context out of the required list (🔴 rule advisory, recorded) rather than making every 🔴 PR unmergeable.
- **`pair-explicit-approval`** — verifies a **non-author human** approval on the current head when the tier requires it (🔴, and untagged ⇒ 🔴 fail-safe); auto-passes at 🟢/🟡 (D10). Generate its job from the host guide's template **unmodified in four respects**, because all four are authorization properties rather than style: it must run from a **trusted ref** (the target branch's version of the job and of any projection it sources — never the pull request's own tree, or the change under review could rewrite the check that authorizes it); its verdict must be **pinned to the PR head commit** on every re-evaluation (including the approval-triggered one), since that is the commit protection evaluates; it must publish a **pending result as its first step**, so a cancelled or aborted re-evaluation leaves a merge-blocking context instead of a stale `success` from a lower tier; and it must keep the **`REVIEW_IDENTITY_LOGIN: ${{ vars.REVIEW_IDENTITY_LOGIN }}` env thread** on the evaluating step — the repository variable that arms `human_approval_jq_filter`'s login clause. Dropping that line is not a style omission: unset, the clause reads `.user.login != ""`, true for every account, so on a repository declaring `Review identity: bot-user` the bot's **own** approving review satisfies this gate and a `risk:red` PR passes `pair-explicit-approval` with no human involvement — on the one surface where the 🔴 gate is actually binding. Emit it even where no identity is configured today (empty ⇒ inert, which is correct). When registering it, **pin the context's producer** where the host allows it (on GitHub: the `checks[].app_id` form, not the legacy `contexts` array) — an unpinned status context can be satisfied by any principal with push access, including the reviewing agent, which would void the 🔴 gate. `pair-review` stays unpinned by construction (no app publishes it): it is an anti-accident control, not an authorization one — see the host guide's "What each context proves". It reads the `risk:*` label only — no classification criteria (D18). **Single-maintainer repositories**: the host rejects a self-approval, so 🔴 PRs cannot satisfy this context without a second human account — say so explicitly and offer to leave the context out of the required list (🔴 rule advisory, recorded) rather than making every 🔴 PR unmergeable.
- Also state the **approval count** explicitly in the protection payload when the host has one (GitHub: `required_approving_review_count: 0`) — the tier-scoped job is the approval authority; an unstated default of ≥1 would demand a human approval on **every** PR, contradicting the 🟢 self-merge row of quality-model §4.
5. **Act — stale-approval invalidation**: enable the host's dismiss-stale-reviews equivalent so a force-push invalidates a previous human approval (pr-states.md edge case).
6. **Act — degraded mode**: if the host has **no required-check / branch-protection API**, or the token lacks permission, do **not** silently continue: report `Required checks: DEGRADED — enforcement advisory` and emit the host guide's **manual** setup steps (which contexts to require, where). Record the gap in [way-of-working.md](../../../.pair/adoption/tech/way-of-working.md)'s Quality Gates section so it is visible until applied.
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/pair-process-implement/post-review-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Disclosed from [SKILL.md](./SKILL.md) Phase 4 — only reached when `/pair-proce
This is the author-side merge path, and it carries the **same** precondition as the reviewer-side one (`/pair-process-review` Phase 6, Step 6.0): the merge is permitted by the **synthesis**, never by "a review happened". An approving verdict alone is not the condition — a 🔴 PR with an approved review and no explicit human approval must not merge here either (D10). See [pr-states.md](../../../.pair/knowledge/guidelines/collaboration/project-management-tool/pr-states.md).

1. **Act**: Re-read the **current** signals on the PR's head commit — gate checks, the review verdict, the `risk:*` tier, and non-author human approvals (they may have changed since the verdict: a new commit, a tier raise, a dismissed approval).
2. **Act**: Synthesize with the shipped evaluator [`pr-state.sh`](../../../.pair/knowledge/assets/pr-state.sh) — `resolve_pr_state <gates> <review> <tier> <explicit_approval>`, then `merge_allowed <state>`. Tier comes from the label via `resolve_tier`; the human-approval input uses `human_approval_jq_filter` (non-bot, non-author, on the current head), never a raw approval count.
2. **Act**: Synthesize with the shipped evaluator [`pr-state.sh`](../../../.pair/knowledge/assets/pr-state.sh) — `resolve_pr_state <gates> <review> <tier> <explicit_approval>`, then `merge_allowed <state>`. Tier comes from the label via `resolve_tier`; the human-approval input uses `human_approval_jq_filter` (non-bot, non-author, on the current head), never a raw approval count. **Evaluate it with `REVIEW_IDENTITY_LOGIN` EXPORTED into that shell** — read it from the host on this run (`gh api "repos/<owner>/<repo>/actions/variables/REVIEW_IDENTITY_LOGIN" --jq .value`), the same value `pair-explicit-approval` resolves as `${{ vars.REVIEW_IDENTITY_LOGIN }}`. **The filter's login clause is inert without it**: unset, `.user.login != env.REVIEW_IDENTITY_LOGIN` compares against the empty string and is true for every account, so a `bot-user` review identity's own APPROVED review — cast outside the flow — counts here as the non-author human approval, `merge_allowed` passes, and a `risk:red` PR merges with no human approval. The CI job that carries the same clause is a different evaluation and only exists where `Review enforcement` is enabled and branch protection applied; it does not cover this path.
3. **Skip**: Review not submitted yet → **HALT**, wait for review completion.
4. **Verify**: State is `ready-to-merge` → continue to Step 4.2. Any other state → **HALT**, naming the unmet condition (red gate, review not approved / still pending, or 🔴 without an explicit human approval). Never bypass, dismiss, or re-run a required check to get a green merge button.

Expand Down
Loading
Loading