This is an automated email from the ASF dual-hosted git repository.
shuke987 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/doris-skills.git
The following commit(s) were added to refs/heads/main by this push:
new 8d6236f fix: require qualified reviewer runtimes (#8)
8d6236f is described below
commit 8d6236ff503eb1d34faef4e6192b054b9c45de94
Author: shuke <[email protected]>
AuthorDate: Thu Sep 3 20:23:21 2026 +0800
fix: require qualified reviewer runtimes (#8)
* fix: require qualified reviewer runtimes
* fix: publish verified local review receipts (#9)
* fix: publish verified local review receipts
* fix: harden verified review receipts
* fix: validate model-specific review effort
* fix: parse review convergence exactly
---
README.md | 11 +-
skills/doris-repo-review/SKILL.md | 103 +++----
.../doris-repo-review/references/doc-templates.md | 2 +-
.../references/pr-comment-format.md | 25 +-
.../references/qualified-runtime.md | 40 +++
.../doris-repo-review/scripts/post-pass-comment.sh | 107 +++----
.../scripts/record-review-runtime.sh | 43 +++
.../scripts/review-runtime-policy.sh | 53 ++++
skills/doris-repo-review/scripts/verify-anchors.py | 172 -----------
.../scripts/verify-review-docs.py | 329 +++++++++++++++++++++
verify/README.md | 10 +
verify/repo-review/post-comment.sh | 149 ++++++++++
verify/repo-review/review-documents.sh | 144 +++++++++
verify/repo-review/run.sh | 7 +
verify/repo-review/runtime-attestation.sh | 59 ++++
verify/repo-review/runtime-policy.sh | 72 +++++
16 files changed, 1033 insertions(+), 293 deletions(-)
diff --git a/README.md b/README.md
index 5c23ff6..4082c3c 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,8 @@ skill from its `description`, so in practice you describe the
problem and the ri
These skills read a Doris **source** checkout and never touch a cluster; the
cluster-side skills
never touch repository source. Nothing here builds Doris or runs tests. The
one GitHub write in
the whole set is `doris-repo-review`'s PASS comment — posted only when the
review finds no
-`Blocker` and no `Major`, and only after the user has seen the exact body and
approved it.
+`Blocker` or `Major`, converges under a qualified reviewer, and still matches
the live PR head.
+Explicitly invoking the skill authorizes that one automatic comment.
## Install
@@ -100,7 +101,8 @@ keep that file and doriscli in sync.
The factual claims in the cluster-side skills are regression-tested — against
a real Apache Doris
cluster, against `doriscli`, and against the skills' own runtime behavior — so
they do not drift
-from reality. See [`verify/`](verify/README.md); it runs in three layers:
+from reality. See [`verify/`](verify/README.md); it runs three
cluster/behavior layers plus a
+repository-review contract suite:
- **L1 — knowledge**: every DDL template and gotcha in `doris-best-practices`
is accepted
or rejected by a live cluster exactly as the skill claims (`mysql` client).
@@ -109,10 +111,11 @@ from reality. See [`verify/`](verify/README.md); it runs
in three layers:
- **L3 — behavior**: the skills *behave* as written — evidence-first and
safety guardrails,
an end-to-end advisor→DDL→live-cluster loopback, and skill-router triggering
— exercised
through a nested `claude -p`.
+- **Repo review**: qualified-runtime policy, document-result validation, and
mocked GitHub comment
+ create/update behavior.
A skill that makes no DDL or doriscli claim (`doris-debug`,
`doris-profile-reader`,
-`doris-repo-review`) has no L1/L2 coverage today. Adding cases for a new skill
is welcome but not
-a merge requirement.
+`doris-repo-review`) has no L1/L2 coverage; its local receipt contract is
covered separately.
## Contributing a skill
diff --git a/skills/doris-repo-review/SKILL.md
b/skills/doris-repo-review/SKILL.md
index 3a1a02f..f260d31 100644
--- a/skills/doris-repo-review/SKILL.md
+++ b/skills/doris-repo-review/SKILL.md
@@ -1,6 +1,6 @@
---
name: doris-repo-review
-description: Given a PR URL (`/doris-repo-review
https://github.com/apache/doris/pull/66807`), first check whether the current
directory's branch and commit match that PR, and if they do not, align the
current directory to the PR head without disturbing local work (refuse to
switch when tracked files are modified and hand the decision back to the user);
then review it with the same pipeline apache/doris CI runs (Code Review Runner)
- main-agent risk scan, 1-3 full-review subagents plus r [...]
+description: Review an Apache Doris PR from a local clone with the same
multi-agent, shared-ledger convergence workflow as the CI Code Review Runner.
Use when the user supplies a PR to `/doris-repo-review`, asks to review a Doris
PR locally, or asks for the CI-style review flow. Safely align only the current
worktree to the exact PR head, refuse to disturb tracked local changes, write
equivalent English and Chinese review documents with verified path-line
anchors, and never build, test, [...]
---
# Local pipeline-style Doris code review
@@ -35,8 +35,10 @@ $S/align-to-pr.sh <PR> --check #
diagnosis only, changes no
$S/align-to-pr.sh <PR> --out "$CTX/align.env" # step 1: align the
current directory
$S/prepare-review-context.sh --ctx "$CTX" --align "$CTX/align.env" # step 2:
gather context
$S/coverage-report.sh --ctx "$CTX" # step 6a: what has nobody
read yet
-python3 $S/verify-anchors.py --ctx "$CTX" --doc <en> --doc <zh> # step 9:
verify anchors
-$S/post-pass-comment.sh --ctx "$CTX" --model <id> ... --dry-run # step
10: PASS comment
+python3 $S/verify-review-docs.py --ctx "$CTX" --doc <en> --doc <zh> # step 9:
verify documents
+$S/record-review-runtime.sh --ctx "$CTX" --model <id> --effort <effort>
+$S/post-pass-comment.sh --ctx "$CTX" # step
10: auto-post PASS
+$S/review-runtime-policy.sh check <model> <effort> #
reviewer eligibility
$S/save-run-state.sh --ctx "$CTX" --verdict ... --findings b,m,mi,n # step
11: leave state for next time
```
@@ -45,12 +47,15 @@ $S/save-run-state.sh --ctx "$CTX" --verdict ... --findings
b,m,mi,n # step 11:
| `scripts/align-to-pr.sh` | Resolve the PR, diagnose how the current
directory relates to it, align it to the PR head |
| `scripts/prepare-review-context.sh` | Produce the authoritative diff,
new-side line ranges, required AGENTS.md list, existing comments, ledger
skeleton |
| `scripts/coverage-report.sh` | Mechanical check of which changed files no
ledger file has mentioned yet |
-| `scripts/verify-anchors.py` | Check that every `path:line` anchor really
exists and that both documents expose the same finding IDs |
+| `scripts/verify-review-docs.py` | Validate commit, anchors, EN/ZH agreement,
verdict, findings, rounds, and convergence |
+| `scripts/record-review-runtime.sh` | Record the qualified reviewer model,
effort, and exact commit |
+| `scripts/review-runtime-policy.sh` | The exact model and effort allowlist
for a pipeline-equivalent review |
| `scripts/post-pass-comment.sh` | Render and post the machine-readable PASS
comment; refuses everything that is not a pass |
| `scripts/save-run-state.sh` | Persist this run's merged ledger under the
stable per-PR state directory, so the next review inherits its dismissals |
| `references/prompts.md` | Subagent prompt templates (CI wording, carried
over verbatim) |
| `references/doc-templates.md` | Templates for both documents, anchor format,
verdict rule |
| `references/pr-comment-format.md` | The `doris-repo-review/v1` comment
schema, field meanings, and how a program reads it back |
+| `references/qualified-runtime.md` | How to select or delegate to a qualified
lead and coverage reviewers |
Requirements: `git`, an authenticated `gh` CLI, `jq`, and `python3`. The clone
must have full
history (`git fetch --unshallow` on a shallow one), because the authoritative
diff is a three-dot
@@ -60,6 +65,11 @@ diff from the merge base.
## 0. Ground rules
+Before reading source, read `references/qualified-runtime.md` and establish a
qualified lead
+reviewer. If it requires delegation, the caller delegates the whole task and
does no review itself.
+If no qualified runtime is available, produce local documents only and do not
post a
+pipeline-equivalent PASS comment.
+
1. **Touch only the current directory, and never disturb local work.** When
the current directory
has **modified tracked files**, refuse to switch, report the situation, and
let the user commit
or stash it themselves - **never stash, reset, or delete a branch on the
user's behalf**, and
@@ -68,9 +78,9 @@ diff from the merge base.
2. **Read-only review.** Do not build, do not run tests, do not modify any
source file in the
repository. The only local writes allowed are the two documents under
`review-docs/` and the
context directory `$CTX`.
- **Exactly one thing may ever be written to GitHub**: the PASS comment of
step 10, only when the
- verdict is APPROVE, only through `post-pass-comment.sh`, and only after the
user has seen the
- rendered body and said go. A REQUEST_CHANGES review posts nothing. No
inline comments, no
+ **Exactly one thing may ever be written to GitHub**: the PASS comment of
step 10, automatically
+ after an explicitly requested review reaches a converged APPROVE result,
and only through
+ `post-pass-comment.sh`. A REQUEST_CHANGES review posts nothing. No inline
comments, no
review submission, no labels, no edits to the PR body - and never a comment
on any PR other
than the one being reviewed.
3. **The diff has exactly one source**: `$CTX/pr.diff` and
`$CTX/pr_changed_files.txt`. Do not
@@ -95,7 +105,7 @@ diff from the merge base.
"Considered and Dismissed". Silently dropping a suspicion means the review
is not finished.
7. **There are no inline comments locally**, so an anchor is the reader's only
way in. Every
finding must carry a `path:line` anchor (new-side line numbers) plus a
verbatim snippet, and
- the documents must pass `verify-anchors.py` at the end.
+ the documents must pass `verify-review-docs.py` at the end.
---
@@ -176,6 +186,15 @@ Output (under `$CTX`):
If `BASE_SOURCE` is not `PR base sha (matches CI)`, the baseline differs from
CI's and the
documents must say so.
+Record the qualified runtime selected in step 0 before reading source:
+
+```bash
+$S/record-review-runtime.sh --ctx "$CTX" --model "<exact model>" --effort
"<exact effort>"
+```
+
+If it refuses the runtime, continue with local documents only and do not post
a pipeline-equivalent
+PASS comment.
+
### 2.1 Prior runs of the same PR are input, not history
A PR is usually reviewed more than once - the author pushes, you re-run. Each
run used to start
@@ -285,7 +304,8 @@ premise is unverified so the subagent checks it first and
stops early if it fail
## 5. Spawn the subagents
Split along the coverage the code-review skill requires, and **send them all
in one message so
-they run concurrently** (general-purpose subagents):
+they run concurrently** (general-purpose subagents). Use the same qualified
model and effort as
+the lead; follow the coverage-reviewer rules in
`references/qualified-runtime.md`:
| Type | Count | Responsibility |
|---|---|---|
@@ -440,7 +460,7 @@ considered last time, and dismissed with what evidence?" -
and once it is gone,
`review-docs/` is untracked. The two unsuffixed names are convenience
symlinks, so anything that
links to them keeps working while the history accumulates behind them.
-Point `verify-anchors.py` at the real files, not the symlinks. If a document
for this exact head
+Point `verify-review-docs.py` at the real files, not the symlinks. If a
document for this exact head
already exists, you are re-running against an unchanged head: overwrite that
one, and only that one.
`references/doc-templates.md` holds the templates, the anchor format, and the
verdict rule
@@ -470,23 +490,22 @@ When they are written, running the verifier is
**mandatory**; if it fails, fix t
re-run until it passes:
```bash
-python3 $S/verify-anchors.py --ctx "$CTX" \
+python3 $S/verify-review-docs.py --ctx "$CTX" \
--doc review-docs/pr-<N>-review.en.md \
--doc review-docs/pr-<N>-review.zh.md
```
-It checks that anchor paths exist, that line numbers are inside the file, that
every finding has at
-least one anchor, and that the EN and ZH finding-ID sets match; it also flags
anchors pointing at
-unchanged context lines - which is usually where a miscomputed line number
shows up.
+It validates the reviewed head, anchors, EN/ZH finding order and severity,
verdict, rounds, and
+convergence. Fix every error before continuing.
---
## 10. Post the PASS comment to the PR
-**Only when the verdict is APPROVE** (no `Blocker`, no `Major`) and
`verify-anchors.py` has
-passed. A REQUEST_CHANGES review posts nothing at all - say so in the closing
report and stop.
+**Only when the verdict is APPROVE**, the review converged, and step 9 passed.
A
+REQUEST_CHANGES or non-converged review posts nothing - say so in the closing
report and stop.
-Write the notes, dry-run, get a go, post:
+Write optional notes and run the poster once:
```bash
# At most 5 bullets, each anchored where it can be. Skip the file when there
is nothing to say.
@@ -495,32 +514,14 @@ cat > "$CTX/pr-comment-notes.md" <<'EOF'
EOF
$S/post-pass-comment.sh --ctx "$CTX" \
- --model "<exact model id of this session>" --effort
"${CLAUDE_EFFORT:-unknown}" \
- --findings <blocker>,<major>,<minor>,<nit> \
- --rounds <r> --converged <true|false> \
- --notes-file "$CTX/pr-comment-notes.md" \
- --dry-run
+ --notes-file "$CTX/pr-comment-notes.md"
```
-- **`--model` is the exact model id of the session doing the review**
(`claude-opus-5[1m]`,
- `gpt-5.6-sol`, …), taken from what this session was told about itself -
never a guess, never a
- bare family name. `--effort` comes from `$CLAUDE_EFFORT`. The comment is a
public, signed
- statement about who reviewed the code; both fields are what make it
auditable.
-- The dry run runs **every** precondition and prints the exact body. **Show
that body to the user
- and wait for a go**, then re-run the identical command **without
`--dry-run`**. Never post
- without that confirmation.
-- The script refuses to post when: any `Blocker`/`Major` is present; the live
PR head no longer
- equals the reviewed commit; the PR is not open (`--allow-closed` overrides);
the notes are
- malformed or more than five; `converged: false` came without a note. A
refusal is a real signal
- - relay it, do not work around it.
-- **The PR head moved** means the author pushed during the review: the review
is stale, so re-run
- the whole skill instead of posting.
-- An earlier v1 comment from the same account for the **same** commit is
edited in place; a new
- commit gets a new comment, so each push leaves exactly one record.
-- Findings counts, rounds and `converged` must match the documents written in
step 9. The counts
- are of *accepted* findings, not of candidates.
-- `references/pr-comment-format.md` holds the schema, the field meanings, and
the parser snippet.
- **Never hand-write or hand-edit this comment** - the format is a contract
other programs read.
+The poster reads model, effort, and commit from `review-runtime.json`; it
invokes
+`verify-review-docs.py` itself for verdict, findings, rounds, and convergence;
then it rechecks the
+live PR head. A refusal is final. The normal flow posts immediately;
`--dry-run` exists only for
+maintainer testing. Same account plus same commit updates the existing
comment, while a new commit
+creates a new one. Keep the schema in `references/pr-comment-format.md`
machine-generated.
---
@@ -538,7 +539,7 @@ Tell the user:
2. The `branch check` / `commit check` results - especially `ahead:N`
(unpushed commits that were
not reviewed).
3. **What happened to the PASS comment**: the URL when one was posted or
updated, or the reason
- nothing was posted (the verdict was REQUEST_CHANGES, the PR head moved, the
user said no).
+ nothing was posted (REQUEST_CHANGES, non-convergence, unqualified runtime,
or moved PR head).
4. **Where the current directory now stands**: with `ALIGN_MODE=switched` it
is detached on the PR
head, and `git checkout <PREV_REF>` restores it. **Do not switch back
automatically** - the user
may still want to read the code.
@@ -564,7 +565,7 @@ Tell the user:
| CI (code-review-runner.yml) | Local |
|---|---|
-| `codex exec --goal` (gpt-5.6-sol, xhigh) | The main agent of this session |
+| `codex exec --goal` (gpt-5.6-sol, xhigh) | An explicitly selected qualified
lead reviewer |
| checkout the PR head sha | `align-to-pr.sh` detaches **the current
directory** to the same sha |
| `git diff BASE...HEAD` as the authoritative diff | Same, produced by
`prepare-review-context.sh` |
| "PR changed while preparing" guard | The fetched sha must equal the API head
sha, otherwise a re-run is required |
@@ -575,7 +576,7 @@ Tell the user:
| Main risk scan → 1-3 full-review subagents + risk-focused → merge → ≤3
rounds | Same shape, three local additions: every risk item carries a premise
check the main agent runs before dispatch (4.1); round 1 goes out in two waves
so the ledger can deduplicate (5.1); a round converges on **severity plus
coverage**, not on "no new candidates at all" (7) |
| CI reviews one push in isolation | Earlier runs of the same PR are loaded
from a stable state directory and read as input (2.1); documents are named by
head sha and never overwritten (9) |
| `gh pr review` / Reviews API posting inline comments | **Two `review-docs/`
documents (EN + ZH) with `path:line` anchors** |
-| CI's review verdict is visible on the PR itself | On a pass, one
`doris-repo-review/v1` comment from the local account (commit sha, timestamp,
model, findings, notes); on REQUEST_CHANGES, nothing - the documents stay local
|
+| CI's review verdict is visible on the PR itself | On a qualified converged
pass, one automatic `doris-repo-review/v1` comment bound to the exact commit;
otherwise nothing |
| 60-minute timeout | No hard timeout, but likewise do not let one round turn
into unbounded digging |
---
@@ -621,13 +622,7 @@ Tell the user:
dropped.
- **Do not commit `review-docs/`.** The doris repository does not ignore it,
and an automatic commit
would slip it into the PR.
-- **The PASS comment is public and signed with the user's name.** It goes to a
public Apache PR
- from their GitHub account, so it is posted only after they have seen the
exact body. Treat a
- script refusal as final rather than something to route around, and never
"tidy up" the rendered
- body by hand - a program reads it.
-- **A pass is not a merge approval.** The comment states that a local pipeline
review found no
- Blocker and no Major on one specific commit. It carries no CI signal and no
Apache sign-off, and
- the `<sub>` disclaimer line says exactly that - keep it.
-- **Counts drift between the documents and the comment.** `--findings` must be
the accepted
- findings of step 9, not the candidate count from the ledger; re-count from
the written documents
- before posting.
+- **The PASS comment is public and signed with the user's name.** Invoking
this skill for a PR
+ authorizes one automatic comment after a qualified converged pass. Never
edit the body by hand.
+- **A pass is not a human merge approval.** Repository policy may accept the
receipt for its exact
+ commit, but it carries no Apache sign-off. Keep the `<sub>` disclaimer.
diff --git a/skills/doris-repo-review/references/doc-templates.md
b/skills/doris-repo-review/references/doc-templates.md
index 17b0c0f..a9ce34a 100644
--- a/skills/doris-repo-review/references/doc-templates.md
+++ b/skills/doris-repo-review/references/doc-templates.md
@@ -22,7 +22,7 @@ their original form in both documents.
Local review has no inline comments, so an anchor is the only way a reader
finds the code.
- Write anchors inside backticks: ``
`fe/fe-core/src/main/java/org/apache/doris/X.java:412` ``
- or `` `path:412-430` ``. `scripts/verify-anchors.py` only recognises this
form.
+ or `` `path:412-430` ``. `scripts/verify-review-docs.py` only recognises
this form.
- Numbers are NEW-SIDE (post-change) line numbers of the worktree at HEAD,
cross-checked
against `changed_line_ranges.txt` and confirmed by reading the file.
- Every finding carries at least one anchor plus a fenced snippet copied
verbatim from it.
diff --git a/skills/doris-repo-review/references/pr-comment-format.md
b/skills/doris-repo-review/references/pr-comment-format.md
index a26488e..edd51d2 100644
--- a/skills/doris-repo-review/references/pr-comment-format.md
+++ b/skills/doris-repo-review/references/pr-comment-format.md
@@ -2,7 +2,7 @@
When a review passes, the skill posts **one comment** on the PR from the
locally authenticated
`gh` account. The comment is written by `scripts/post-pass-comment.sh`, never
typed by hand: the
-agent supplies the numbers and the notes, the script owns the layout. Anything
that reads the
+agent supplies optional notes, while verified documents supply the structured
result. Anything that reads the
comment back — a script, a dashboard, another agent — depends on that layout
being fixed.
## Layout
@@ -42,8 +42,8 @@ converged: true
- The behaviour change is gated by `ldap_authentication_enabled`, so no
rolling-upgrade path is
required.
-<sub>Reviewed locally with the `doris-repo-review` pipeline (a local port of
-`.github/workflows/code-review-runner.yml`). This is not a CI status
check.</sub>
+<sub>Reviewed locally with the `doris-repo-review` pipeline. Repository policy
may accept this
+receipt for the matching commit; it is not a human Apache approval.</sub>
<!-- doris-repo-review:v1:end -->
````
@@ -58,11 +58,11 @@ converged: true
| `base` | 40-hex | `BASE_SHA` — with `commit` this reproduces the reviewed
diff |
| `reviewed_at` | ISO-8601, minute precision, with offset | when the comment
was rendered |
| `reviewer` | GitHub login | `gh api user`, falling back to GraphQL `viewer`
and `gh auth status` |
-| `model` | exact model id | passed with `--model`, e.g. `claude-opus-5[1m]`,
`gpt-5.6-sol` |
-| `effort` | reasoning effort | `--effort`, default `$CLAUDE_EFFORT` |
-| `findings` | inline map | counts per severity; `blocker` and `major` are
always 0 in a PASS |
-| `rounds` | int | convergence rounds actually run (1-3) |
-| `converged` | bool | `false` means the 3-round cap was hit with candidates
still open |
+| `model` | exact eligible model id | `review-runtime.json`, recorded before
source review |
+| `effort` | model-supported qualified effort: `xhigh` or `max`, plus `ultra`
for Codex | `review-runtime.json` |
+| `findings` | inline map | `verify-review-docs.py`; `blocker` and `major` are
always 0 in a PASS |
+| `rounds` | int | `verify-review-docs.py` (1-3) |
+| `converged` | bool | `verify-review-docs.py`; only `true` is posted |
Rules the script enforces, so they cannot drift:
@@ -70,7 +70,11 @@ Rules the script enforces, so they cannot drift:
refuses to post — a failing review leaves no trace on GitHub.
- **The PR head must not have moved.** The live `head.sha` is re-read and must
equal `commit`,
otherwise the comment would sign off a commit nobody reviewed.
-- **`converged: false` requires at least one note** saying what was left
unexamined.
+- **Runtime and documents must name the same commit.** Both must equal
`meta.env` and the live PR
+ head. Model and effort must pass the exact allowlist.
+- **The documents are the result source.** The poster invokes their verifier
immediately before
+ rendering; command-line overrides for verdict, counts, rounds, or
convergence do not exist.
+- **The review must converge.** A non-converged review never posts a PASS
receipt.
- **At most 5 notes**, each a `- ` bullet (2-space indented continuation lines
allowed). Longer
material belongs in the local review documents.
- **Same commit ⇒ update in place.** An earlier v1 comment by the same account
carrying the same
@@ -114,7 +118,8 @@ place only when a maintainer would act differently without
it:
- a residual risk that did not reach `Minor`, with a `path:line` anchor;
- coverage the review could not reach (no build, no cluster, no test run);
- a backport or upgrade consideration the PR itself does not state;
-- with `converged: false`, what was still open when the round cap hit.
+
+A non-converged review records what remained open in the local documents and
posts no receipt.
Not this: restating what the PR does, listing every `Minor`/`Nit` (they live
in the documents),
praise, or anything that reads as an official Apache sign-off. `_None._` is a
perfectly good
diff --git a/skills/doris-repo-review/references/qualified-runtime.md
b/skills/doris-repo-review/references/qualified-runtime.md
new file mode 100644
index 0000000..e5deeda
--- /dev/null
+++ b/skills/doris-repo-review/references/qualified-runtime.md
@@ -0,0 +1,40 @@
+# Qualified reviewer runtime
+
+Establish the reviewer runtime before reading source. A pipeline-equivalent
review accepts only:
+
+| Host | Models | Effort |
+|---|---|---|
+| Codex | `gpt-5.6-sol` | `xhigh`, `max`, or `ultra` |
+| Claude Code | `claude-opus-5`, `claude-opus-5[1m]`, `claude-fable-5`, or
`claude-fable-5[1m]` | `xhigh` or `max` |
+
+Validate the exact values with:
+
+```bash
+$S/review-runtime-policy.sh check "<model>" "<effort>"
+```
+
+Keep these exact values for `record-review-runtime.sh` after the PR context is
prepared.
+
+Never infer task settings from a global config file. Continue in the current
task only when its
+model and effort are exposed explicitly and pass the policy check.
+
+## Codex delegation
+
+When either value is unavailable or ineligible, spawn one lead reviewer with
exactly:
+
+- `fork_turns: "none"`
+- `model: "gpt-5.6-sol"`
+- `reasoning_effort: "xhigh"`
+
+Pass the original request, current working directory, and absolute path to
this skill. Tell the
+lead to execute the whole skill. Wait for it and relay its result; do not read
source or repeat the
+review in the parent. A full-history fork cannot apply model or effort
overrides.
+
+## Coverage reviewers
+
+Use the same qualified model and effort for every substantive coverage
reviewer. On Codex, use
+`fork_turns: "none"` with explicit overrides and include all required paths,
SHAs, focus points,
+prompt text, and ledger destinations in each message.
+
+Runtime identity is an auditable local attestation, not a cryptographic proof.
If the host cannot
+guarantee these settings, complete the local documents but do not post a
pipeline-equivalent PASS.
diff --git a/skills/doris-repo-review/scripts/post-pass-comment.sh
b/skills/doris-repo-review/scripts/post-pass-comment.sh
index fcfd6a0..0e769e7 100755
--- a/skills/doris-repo-review/scripts/post-pass-comment.sh
+++ b/skills/doris-repo-review/scripts/post-pass-comment.sh
@@ -2,49 +2,27 @@
# Render and post the machine-readable PASS comment of a doris-repo-review run.
#
# Usage:
-# post-pass-comment.sh --ctx <dir> --model <id> [options]
+# post-pass-comment.sh --ctx <dir> [options]
#
-# --ctx <dir> review context directory (must contain meta.env)
-# --model <id> exact model id of the reviewing agent, e.g.
claude-opus-5[1m]
-# --effort <s> reasoning effort (default: $CLAUDE_EFFORT, else
"unknown")
-# --findings b,m,mi,n blocker,major,minor,nit counts (default 0,0,0,0)
-# --rounds <n> convergence rounds actually run (default 1)
-# --converged true|false (default true)
+# --ctx <dir> review context with meta.env, review-runtime.json,
and EN/ZH docs
# --notes-file <f> markdown bullet list for "Notes for maintainers"
# --dry-run run every precondition, render the body, post
nothing
-# --force-new always create a new comment, never update in place
-# --allow-closed allow posting on a non-open PR
#
-# Only a PASS is ever posted: blocker and major counts must both be 0, which is
-# the same verdict rule the review documents use. The body layout is fixed here
-# on purpose - the agent supplies the notes, never the format.
+# Runtime fields come from review-runtime.json. Review fields come directly
from
+# verify-review-docs.py. The agent supplies notes, never receipt fields or
format.
#
# Rendered body: <ctx>/pr-comment.md Posted URL: <ctx>/pr-comment.url
set -euo pipefail
CTX=""
-MODEL=""
-EFFORT="${CLAUDE_EFFORT:-unknown}"
-FINDINGS="0,0,0,0"
-ROUNDS="1"
-CONVERGED="true"
NOTES_FILE=""
DRY_RUN=0
-FORCE_NEW=0
-ALLOW_CLOSED=0
while [ $# -gt 0 ]; do
case "$1" in
--ctx) CTX="$2"; shift 2 ;;
- --model) MODEL="$2"; shift 2 ;;
- --effort) EFFORT="$2"; shift 2 ;;
- --findings) FINDINGS="$2"; shift 2 ;;
- --rounds) ROUNDS="$2"; shift 2 ;;
- --converged) CONVERGED="$2"; shift 2 ;;
--notes-file) NOTES_FILE="$2"; shift 2 ;;
--dry-run) DRY_RUN=1; shift ;;
- --force-new) FORCE_NEW=1; shift ;;
- --allow-closed) ALLOW_CLOSED=1; shift ;;
-h|--help) sed -n '2,25p' "$0"; exit 0 ;;
*) echo "unknown argument: $1" >&2; exit 2 ;;
esac
@@ -58,7 +36,8 @@ SCHEMA='doris-repo-review/v1'
[ -n "$CTX" ] || { echo "ERROR: --ctx is required." >&2; exit 2; }
META="$CTX/meta.env"
[ -f "$META" ] || { echo "ERROR: $META not found - run
prepare-review-context.sh first." >&2; exit 2; }
-[ -n "$MODEL" ] || { echo "ERROR: --model is required; state the exact model
id, never a guess." >&2; exit 2; }
+RUNTIME_FILE="$CTX/review-runtime.json"
+[ -f "$RUNTIME_FILE" ] || { echo "ERROR: $RUNTIME_FILE not found - record the
qualified reviewer first." >&2; exit 2; }
command -v gh >/dev/null 2>&1 || { echo "ERROR: gh CLI is required." >&2; exit
1; }
command -v jq >/dev/null 2>&1 || { echo "ERROR: jq is required." >&2; exit 1; }
command -v python3 >/dev/null 2>&1 || { echo "ERROR: python3 is required."
>&2; exit 1; }
@@ -69,26 +48,48 @@ PR_NUMBER="$(read_meta PR_NUMBER)"
PR_URL="$(read_meta PR_URL)"
BASE_SHA="$(read_meta BASE_SHA)"
HEAD_SHA="$(read_meta HEAD_SHA)"
+NORMALIZED_HEAD_SHA="$(printf '%s' "$HEAD_SHA" | tr '[:upper:]' '[:lower:]')"
REPO_ROOT="$(read_meta REPO_ROOT)"
+DOCS_ROOT="$(read_meta DOCS_ROOT)"
[ -n "$PR_NUMBER" ] || { echo "ERROR: meta.env has no PR_NUMBER - this review
is not attached to a PR." >&2; exit 2; }
[ -n "$UPSTREAM_REPO" ] || { echo "ERROR: meta.env has no UPSTREAM_REPO." >&2;
exit 2; }
[ -n "$PR_URL" ] ||
PR_URL="https://github.com/${UPSTREAM_REPO}/pull/${PR_NUMBER}"
-case "$CONVERGED" in true|false) ;; *) echo "ERROR: --converged takes true or
false." >&2; exit 2 ;; esac
-[[ "$ROUNDS" =~ ^[0-9]+$ ]] || { echo "ERROR: --rounds takes a number." >&2;
exit 2; }
-IFS=',' read -r F_BLOCKER F_MAJOR F_MINOR F_NIT <<<"$FINDINGS"
-for v in "$F_BLOCKER" "$F_MAJOR" "$F_MINOR" "$F_NIT"; do
- [[ "$v" =~ ^[0-9]+$ ]] || { echo "ERROR: --findings takes
blocker,major,minor,nit as four numbers." >&2; exit 2; }
-done
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+MODEL="$(jq -er '.model | strings' "$RUNTIME_FILE")" || { echo "ERROR: invalid
runtime model." >&2; exit 2; }
+EFFORT="$(jq -er '.effort | strings' "$RUNTIME_FILE")" || { echo "ERROR:
invalid runtime effort." >&2; exit 2; }
+RUNTIME_COMMIT="$(jq -er '.commit | strings' "$RUNTIME_FILE")" || { echo
"ERROR: invalid runtime commit." >&2; exit 2; }
+"$SCRIPT_DIR/review-runtime-policy.sh" check "$MODEL" "$EFFORT" >/dev/null
+[ "$RUNTIME_COMMIT" = "$NORMALIZED_HEAD_SHA" ] || {
+ echo "ERROR: qualified reviewer runtime was recorded for $RUNTIME_COMMIT,
not $NORMALIZED_HEAD_SHA." >&2
+ exit 2
+}
-# The verdict rule: any Blocker or Major means REQUEST_CHANGES, and a
-# REQUEST_CHANGES review posts nothing at all.
-if [ "$F_BLOCKER" -gt 0 ] || [ "$F_MAJOR" -gt 0 ]; then
- echo "ERROR: $F_BLOCKER blocker(s) and $F_MAJOR major(s) mean
REQUEST_CHANGES." >&2
- echo " This skill posts a comment only when the review passes.
Nothing was posted." >&2
+[ -n "$DOCS_ROOT" ] || DOCS_ROOT="$REPO_ROOT"
+EN_DOC="$DOCS_ROOT/review-docs/pr-${PR_NUMBER}-review.en.md"
+ZH_DOC="$DOCS_ROOT/review-docs/pr-${PR_NUMBER}-review.zh.md"
+RESULT_JSON="$(python3 "$SCRIPT_DIR/verify-review-docs.py" --json --ctx "$CTX"
\
+ --doc "$EN_DOC" --doc "$ZH_DOC")" || {
+ echo "ERROR: review documents failed verification. Nothing was posted." >&2
+ exit 2
+}
+RESULT_COMMIT="$(jq -er '.commit | strings' <<<"$RESULT_JSON")"
+VERDICT="$(jq -er '.verdict | strings' <<<"$RESULT_JSON")"
+ROUNDS="$(jq -er '.rounds | numbers' <<<"$RESULT_JSON")"
+CONVERGED="$(jq -r '.converged | if . == true then "true" else "false" end'
<<<"$RESULT_JSON")"
+F_BLOCKER="$(jq -er '.findings.blocker | numbers' <<<"$RESULT_JSON")"
+F_MAJOR="$(jq -er '.findings.major | numbers' <<<"$RESULT_JSON")"
+F_MINOR="$(jq -er '.findings.minor | numbers' <<<"$RESULT_JSON")"
+F_NIT="$(jq -er '.findings.nit | numbers' <<<"$RESULT_JSON")"
+
+[ "$RESULT_COMMIT" = "$NORMALIZED_HEAD_SHA" ] || { echo "ERROR: review
documents target another commit." >&2; exit 2; }
+[ "$VERDICT" = "APPROVE" ] || { echo "ERROR: review verdict is $VERDICT.
Nothing was posted." >&2; exit 1; }
+[ "$CONVERGED" = "true" ] || { echo "ERROR: review did not converge. Nothing
was posted." >&2; exit 1; }
+[ "$F_BLOCKER" -eq 0 ] && [ "$F_MAJOR" -eq 0 ] || {
+ echo "ERROR: Blocker or Major findings cannot produce a PASS comment." >&2
exit 1
-fi
+}
# -------------------------------------------------------------------------
notes
NOTES_BODY="_None._"
@@ -100,11 +101,14 @@ if [ -n "$NOTES_FILE" ]; then
echo "ERROR: the notes file contains a v1 marker; that would break
comment detection." >&2
exit 2
fi
- while IFS= read -r line; do
+ while IFS= read -r line || [ -n "$line" ]; do
[ -n "${line//[[:space:]]/}" ] || continue
case "$line" in
"- "*) NOTE_COUNT=$((NOTE_COUNT + 1)) ;;
- " "*) : ;; # continuation of the previous bullet
+ " "*) [ "$NOTE_COUNT" -gt 0 ] || {
+ echo "ERROR: a note continuation must follow a '- '
bullet." >&2
+ exit 2
+ } ;;
*) echo "ERROR: every note line must be a '- ' bullet or a
2-space continuation: $line" >&2; exit 2 ;;
esac
done < "$NOTES_FILE"
@@ -112,11 +116,6 @@ if [ -n "$NOTES_FILE" ]; then
NOTES_BODY="$(cat "$NOTES_FILE")"
fi
fi
-if [ "$CONVERGED" = "false" ] && [ "$NOTE_COUNT" -eq 0 ]; then
- echo "ERROR: converged=false needs at least one note saying what was left
open." >&2
- exit 2
-fi
-
# ------------------------------------------------------------------ live PR
state
PR_TSV="$(gh api "repos/${UPSTREAM_REPO}/pulls/${PR_NUMBER}" --jq '[.head.sha,
.state] | @tsv')" || {
echo "ERROR: cannot read ${UPSTREAM_REPO}#${PR_NUMBER}." >&2; exit 1; }
@@ -129,8 +128,8 @@ if [ "$LIVE_HEAD_SHA" != "$HEAD_SHA" ]; then
echo " Posting would sign off a commit that was never reviewed.
Re-run the review." >&2
exit 1
fi
-if [ "$LIVE_STATE" != "open" ] && [ "$ALLOW_CLOSED" = "0" ]; then
- echo "ERROR: ${UPSTREAM_REPO}#${PR_NUMBER} is '$LIVE_STATE', not open.
Pass --allow-closed to post anyway." >&2
+if [ "$LIVE_STATE" != "open" ]; then
+ echo "ERROR: ${UPSTREAM_REPO}#${PR_NUMBER} is '$LIVE_STATE', not open." >&2
exit 1
fi
@@ -163,7 +162,7 @@ REVIEWED_AT="$(python3 -c "import datetime;
print(datetime.datetime.now().astime
# ------------------------------------------------------------------ render
body
FENCE='```'
-DISCLAIMER='<sub>Reviewed locally with the `doris-repo-review` pipeline (a
local port of `.github/workflows/code-review-runner.yml`). This is not a CI
status check.</sub>'
+DISCLAIMER='<sub>Reviewed locally with the `doris-repo-review` pipeline.
Repository policy may accept this receipt for the matching commit; it is not a
human Apache approval.</sub>'
BODY_FILE="$CTX/pr-comment.md"
cat > "$BODY_FILE" <<EOF
${BEGIN_MARKER}
@@ -199,6 +198,11 @@ EOF
SAME_ID=""
LAST_ID=""
LAST_COMMIT=""
+COMMENTS_TSV="$(gh api "repos/${UPSTREAM_REPO}/issues/${PR_NUMBER}/comments"
--paginate \
+ --jq '.[] | select(.body | test("doris-repo-review:v1:begin")) | [ .id,
.user.login, ([.body | scan("commit: [0-9a-f]{40}")] | .[0] // "") ] | @tsv')"
|| {
+ echo "ERROR: cannot list existing review comments. Nothing was posted." >&2
+ exit 1
+}
while IFS=$'\t' read -r cid clogin ccommit; do
[ -n "$cid" ] || continue
[ "$clogin" = "$REVIEWER" ] || continue
@@ -206,12 +210,11 @@ while IFS=$'\t' read -r cid clogin ccommit; do
LAST_ID="$cid"
LAST_COMMIT="$ccommit"
[ "$ccommit" = "$HEAD_SHA" ] && SAME_ID="$cid"
-done < <(gh api "repos/${UPSTREAM_REPO}/issues/${PR_NUMBER}/comments"
--paginate \
- --jq '.[] | select(.body | test("doris-repo-review:v1:begin")) | [
.id, .user.login, ([.body | scan("commit: [0-9a-f]{40}")] | .[0] // "") ] |
@tsv' 2>/dev/null || true)
+done <<<"$COMMENTS_TSV"
ACTION="create"
EXISTING_ID=""
-if [ -n "$SAME_ID" ] && [ "$FORCE_NEW" = "0" ]; then
+if [ -n "$SAME_ID" ]; then
ACTION="update"
EXISTING_ID="$SAME_ID"
fi
diff --git a/skills/doris-repo-review/scripts/record-review-runtime.sh
b/skills/doris-repo-review/scripts/record-review-runtime.sh
new file mode 100755
index 0000000..a4bedb8
--- /dev/null
+++ b/skills/doris-repo-review/scripts/record-review-runtime.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# Record the qualified reviewer model, effort, and reviewed commit.
+set -euo pipefail
+
+CTX=""
+MODEL=""
+EFFORT=""
+while [ $# -gt 0 ]; do
+ case "$1" in
+ --ctx) CTX="$2"; shift 2 ;;
+ --model) MODEL="$2"; shift 2 ;;
+ --effort) EFFORT="$2"; shift 2 ;;
+ -h|--help) sed -n '2,24p' "$0"; exit 0 ;;
+ *) echo "unknown argument: $1" >&2; exit 2 ;;
+ esac
+done
+
+[ -n "$CTX" ] || { echo "ERROR: --ctx is required." >&2; exit 2; }
+[ -n "$MODEL" ] || { echo "ERROR: --model is required." >&2; exit 2; }
+[ -n "$EFFORT" ] || { echo "ERROR: --effort is required." >&2; exit 2; }
+command -v jq >/dev/null 2>&1 || { echo "ERROR: jq is required." >&2; exit 1; }
+
+META="$CTX/meta.env"
+[ -f "$META" ] || { echo "ERROR: $META not found - run
prepare-review-context.sh first." >&2; exit 2; }
+HEAD_SHA="$(sed -n 's/^HEAD_SHA=//p' "$META" | head -n 1)"
+[[ "$HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]] || { echo "ERROR: meta.env HEAD_SHA is
not a full SHA." >&2; exit 2; }
+NORMALIZED_HEAD_SHA="$(printf '%s' "$HEAD_SHA" | tr '[:upper:]' '[:lower:]')"
+
+RUNTIME_FILE="$CTX/review-runtime.json"
+rm -f "$RUNTIME_FILE" "$CTX/pr-comment.md" "$CTX/pr-comment.url"
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+# shellcheck source=review-runtime-policy.sh
+. "$SCRIPT_DIR/review-runtime-policy.sh"
+check_review_runtime "$MODEL" "$EFFORT"
+
+TMP_FILE="$(mktemp "$CTX/.review-runtime.XXXXXX")"
+trap 'rm -f "$TMP_FILE"' EXIT
+jq -n --arg model "$MODEL" --arg effort "$EFFORT" --arg commit
"$NORMALIZED_HEAD_SHA" \
+ '{model: $model, effort: $effort, commit: $commit}' > "$TMP_FILE"
+mv "$TMP_FILE" "$RUNTIME_FILE"
+trap - EXIT
+
+echo "recorded qualified review runtime: $MODEL ($EFFORT), commit
$NORMALIZED_HEAD_SHA"
diff --git a/skills/doris-repo-review/scripts/review-runtime-policy.sh
b/skills/doris-repo-review/scripts/review-runtime-policy.sh
new file mode 100755
index 0000000..5d172d8
--- /dev/null
+++ b/skills/doris-repo-review/scripts/review-runtime-policy.sh
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+# Exact reviewer allowlist for a pipeline-equivalent local Doris review.
+
+is_eligible_review_model() {
+ case "$1" in
+
claude-opus-5|claude-opus-5\[1m\]|claude-fable-5|claude-fable-5\[1m\]|gpt-5.6-sol)
return 0 ;;
+ *) return 1 ;;
+ esac
+}
+
+is_eligible_review_effort() {
+ local model="$1"
+ local effort="$2"
+ case "$model" in
+ claude-opus-5|claude-opus-5\[1m\]|claude-fable-5|claude-fable-5\[1m\])
+ case "$effort" in
+ xhigh|max) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ gpt-5.6-sol)
+ case "$effort" in
+ xhigh|max|ultra) return 0 ;;
+ *) return 1 ;;
+ esac
+ ;;
+ *) return 1 ;;
+ esac
+}
+
+check_review_runtime() {
+ local model="$1"
+ local effort="$2"
+ is_eligible_review_model "$model" || {
+ echo "ERROR: model '$model' is not eligible for pipeline-equivalent
review." >&2
+ return 1
+ }
+ is_eligible_review_effort "$model" "$effort" || {
+ echo "ERROR: effort '$effort' is not eligible for model '$model'." >&2
+ echo " Claude Code supports xhigh or max; Codex also supports
ultra." >&2
+ return 1
+ }
+}
+
+if [ "${BASH_SOURCE[0]}" = "$0" ]; then
+ set -euo pipefail
+ [ "${1:-}" = "check" ] && [ $# -eq 3 ] || {
+ echo "usage: $0 check <model> <effort>" >&2
+ exit 2
+ }
+ check_review_runtime "$2" "$3"
+ echo "eligible review runtime: $2 ($3)"
+fi
diff --git a/skills/doris-repo-review/scripts/verify-anchors.py
b/skills/doris-repo-review/scripts/verify-anchors.py
deleted file mode 100755
index 8eefd3d..0000000
--- a/skills/doris-repo-review/scripts/verify-anchors.py
+++ /dev/null
@@ -1,172 +0,0 @@
-#!/usr/bin/env python3
-"""Verify the `path:line` anchors of the doris-repo-review output documents.
-
-Local review has no inline GitHub comments, so every finding must carry an
-anchor the reader can jump to. This script proves the anchors are real:
-
- * the path exists in the worktree at HEAD;
- * the line number is inside the file;
- * (informational) whether the line falls inside a range this PR touched;
- * the EN and ZH documents expose the same finding IDs, and every finding
- carries at least one anchor.
-
-Anchors must be written inside backticks as `path:line` or `path:start-end`.
-
-Usage:
- verify-anchors.py --ctx <context-dir> --doc <en.md> --doc <zh.md>
-
-Exits non-zero when an anchor is broken or the two documents disagree.
-"""
-
-from __future__ import annotations
-
-import argparse
-import re
-import sys
-from collections import defaultdict
-from pathlib import Path
-
-ANCHOR_RE =
re.compile(r"`([A-Za-z0-9_][A-Za-z0-9_./+-]*\.[A-Za-z0-9_+-]+):(\d+)(?:-(\d+))?`")
-FINDING_RE = re.compile(r"^#{2,4}\s+(?:\[)?(F-\d+)(?:\])?\b")
-CODE_FENCE_RE = re.compile(r"^\s*```")
-
-
-def parse_args() -> argparse.Namespace:
- parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
- parser.add_argument("--ctx", required=True, type=Path, help="Review
context directory (holds meta.env).")
- parser.add_argument("--doc", required=True, action="append", type=Path,
help="Review document; repeat for EN and ZH.")
- parser.add_argument("--repo-root", type=Path, default=None, help="Repo
root; defaults to REPO_ROOT from meta.env.")
- return parser.parse_args()
-
-
-def read_meta(ctx: Path) -> dict[str, str]:
- meta_path = ctx / "meta.env"
- if not meta_path.is_file():
- sys.exit(f"missing {meta_path}; run prepare-review-context.sh first")
- meta = {}
- for line in meta_path.read_text(encoding="utf-8").splitlines():
- if "=" in line:
- key, _, value = line.partition("=")
- meta[key] = value
- return meta
-
-
-def load_changed_ranges(ctx: Path) -> dict[str, list[tuple[int, int]]]:
- ranges: dict[str, list[tuple[int, int]]] = defaultdict(list)
- tsv = ctx / "changed_line_ranges.tsv"
- if tsv.is_file():
- for line in tsv.read_text(encoding="utf-8").splitlines():
- parts = line.split("\t")
- if len(parts) == 3:
- ranges[parts[0]].append((int(parts[1]), int(parts[2])))
- return ranges
-
-
-def scan_document(doc: Path) -> tuple[list[str], dict[str, list[tuple[int,
int, int | None]]], list[tuple[int, int, int | None]]]:
- """Return (finding ids in order, anchors per finding, all anchors)."""
- findings: list[str] = []
- per_finding: dict[str, list[tuple[int, int, int | None]]] =
defaultdict(list)
- all_anchors: list[tuple[str, int, int | None, int]] = []
- current: str | None = None
- in_fence = False
-
- for lineno, line in
enumerate(doc.read_text(encoding="utf-8").splitlines(), start=1):
- if CODE_FENCE_RE.match(line):
- in_fence = not in_fence
- continue
- heading = FINDING_RE.match(line)
- if heading:
- current = heading.group(1)
- if current not in findings:
- findings.append(current)
- if in_fence:
- continue
- for match in ANCHOR_RE.finditer(line):
- path, start, end = match.group(1), int(match.group(2)),
match.group(3)
- anchor = (path, start, int(end) if end else None, lineno)
- all_anchors.append(anchor)
- if current:
- per_finding[current].append(anchor)
- return findings, per_finding, all_anchors
-
-
-def in_changed_range(ranges: list[tuple[int, int]], start: int, end: int |
None) -> bool:
- last = end or start
- return any(not (last < lo or start > hi) for lo, hi in ranges)
-
-
-def main() -> int:
- args = parse_args()
- ctx = args.ctx.resolve()
- meta = read_meta(ctx)
- repo_root = (args.repo_root or Path(meta.get("REPO_ROOT", "."))).resolve()
- changed_ranges = load_changed_ranges(ctx)
-
- errors: list[str] = []
- warnings: list[str] = []
- finding_sets: dict[Path, list[str]] = {}
-
- for doc in args.doc:
- doc = doc.resolve()
- if not doc.is_file():
- errors.append(f"{doc}: document not found")
- continue
- findings, per_finding, anchors = scan_document(doc)
- finding_sets[doc] = findings
-
- print(f"\n=== {doc} ===")
- print(f"findings: {len(findings)} anchors: {len(anchors)}")
-
- for path, start, end, lineno in anchors:
- target = repo_root / path
- if not target.is_file():
- errors.append(f"{doc}:{lineno}: anchor path does not exist ->
{path}")
- continue
- try:
- total = sum(1 for _ in target.open(encoding="utf-8",
errors="replace"))
- except OSError as exc:
- errors.append(f"{doc}:{lineno}: cannot read {path}: {exc}")
- continue
- last = end or start
- if start < 1 or last > total:
- errors.append(f"{doc}:{lineno}: {path}:{start}{'-' + str(end)
if end else ''} out of range (file has {total} lines)")
- continue
- if path in changed_ranges and not
in_changed_range(changed_ranges[path], start, end):
- warnings.append(f"{doc}:{lineno}: {path}:{start}{'-' +
str(end) if end else ''} is context, not a changed line (fine if intentional)")
- elif path not in changed_ranges:
- warnings.append(f"{doc}:{lineno}: {path} is not in this PR's
changed-file set (fine for upstream/downstream evidence)")
-
- for finding in findings:
- if not per_finding.get(finding):
- errors.append(f"{doc}: finding {finding} has no `path:line`
anchor")
-
- docs = list(finding_sets)
- if len(docs) >= 2:
- base_doc, base_ids = docs[0], set(finding_sets[docs[0]])
- for other in docs[1:]:
- other_ids = set(finding_sets[other])
- missing = sorted(base_ids - other_ids)
- extra = sorted(other_ids - base_ids)
- if missing:
- errors.append(f"{other}: missing finding IDs present in
{base_doc.name}: {', '.join(missing)}")
- if extra:
- errors.append(f"{other}: has finding IDs absent from
{base_doc.name}: {', '.join(extra)}")
-
- if warnings:
- print("\n--- notes ---")
- for note in warnings:
- print(f" note: {note}")
-
- if errors:
- print("\n--- errors ---")
- for error in errors:
- print(f" ERROR: {error}")
- print(f"\nFAILED: {len(errors)} anchor problem(s).")
- return 1
-
- print("\nOK: every anchor resolves and both documents expose the same
findings.")
- return 0
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/skills/doris-repo-review/scripts/verify-review-docs.py
b/skills/doris-repo-review/scripts/verify-review-docs.py
new file mode 100755
index 0000000..bcebccf
--- /dev/null
+++ b/skills/doris-repo-review/scripts/verify-review-docs.py
@@ -0,0 +1,329 @@
+#!/usr/bin/env python3
+"""Validate both doris-repo-review documents and emit their agreed result."""
+
+from __future__ import annotations
+
+import argparse
+import json
+import re
+import sys
+from collections import defaultdict
+from dataclasses import dataclass, field
+from pathlib import Path
+
+ANCHOR_RE =
re.compile(r"`([A-Za-z0-9_][A-Za-z0-9_./+-]*\.[A-Za-z0-9_+-]+):(\d+)(?:-(\d+))?`")
+FINDING_RE = re.compile(r"^#{2,4}\s+(?:\[)?(F-\d+)(?:\])?\b")
+SEVERITY_RE = re.compile(
+ r"^-\s+\*\*(?:Severity|等级)\*\*\s*[::]\s*(Blocker|Major|Minor|Nit)\s*$",
+ re.IGNORECASE,
+)
+HEAD_RE = re.compile(r"^\|\s*PR head\s*\|\s*`([0-9a-fA-F]{40})`")
+VERDICT_RE =
re.compile(r"^\|\s*(?:Verdict|结论)\s*\|\s*\*\*(APPROVE|REQUEST_CHANGES)\*\*\s*\|")
+ROUNDS_RE = re.compile(r"^\|\s*(?:Rounds|轮次)\s*\|(.*?)\|\s*$")
+EN_ROUNDS_VALUE_RE = re.compile(
+ r"""
+ \s*(?P<rounds>\d+)\s+of\s+max\s+3\s*[,;;]\s*
+ (?:
+ (?P<positive>converged)(?:\s*\([^()\n]*\))?
+ |
+ (?P<negative>did\s+not\s+converge)(?:\s*\([^()\n]*\))?
+ )\s*
+ """,
+ re.IGNORECASE | re.VERBOSE,
+)
+ZH_ROUNDS_VALUE_RE = re.compile(
+ r"""
+
\s*共\s*(?P<rounds>\d+)\s*轮\s*(?:(\s*上限\s*3\s*)|\(\s*上限\s*3\s*\))\s*[,,;;]\s*
+ (?:
+ (?P<positive>已收敛)(?:\s*(?:([^()()\n]*)|\([^()()\n]*\)))?
+ |
+ (?P<negative>未收敛)(?:\s*(?:([^()()\n]*)|\([^()()\n]*\)))?
+ )\s*
+ """,
+ re.VERBOSE,
+)
+CODE_FENCE_RE = re.compile(r"^\s*```")
+SEVERITIES = ("Blocker", "Major", "Minor", "Nit")
+
+
+@dataclass(frozen=True)
+class Anchor:
+ path: str
+ start: int
+ end: int | None
+ source_line: int
+
+
+@dataclass
+class Document:
+ path: Path
+ head_sha: str | None = None
+ verdict: str | None = None
+ rounds: int | None = None
+ converged: bool | None = None
+ finding_ids: list[str] = field(default_factory=list)
+ severities: dict[str, str] = field(default_factory=dict)
+ anchors: list[Anchor] = field(default_factory=list)
+ finding_anchors: dict[str, list[Anchor]] = field(default_factory=lambda:
defaultdict(list))
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--ctx", required=True, type=Path)
+ parser.add_argument("--doc", required=True, action="append", type=Path)
+ parser.add_argument("--repo-root", type=Path)
+ parser.add_argument("--json", action="store_true", help="Print only the
validated result as JSON.")
+ return parser.parse_args()
+
+
+def read_meta(ctx: Path) -> dict[str, str]:
+ path = ctx / "meta.env"
+ if not path.is_file():
+ raise ValueError(f"missing {path}; run prepare-review-context.sh
first")
+ meta: dict[str, str] = {}
+ for line in path.read_text(encoding="utf-8").splitlines():
+ if "=" in line:
+ key, _, value = line.partition("=")
+ meta[key] = value
+ return meta
+
+
+def load_changed_ranges(ctx: Path) -> dict[str, list[tuple[int, int]]]:
+ result: dict[str, list[tuple[int, int]]] = defaultdict(list)
+ path = ctx / "changed_line_ranges.tsv"
+ if not path.is_file():
+ return result
+ for line in path.read_text(encoding="utf-8").splitlines():
+ parts = line.split("\t")
+ if len(parts) == 3:
+ result[parts[0]].append((int(parts[1]), int(parts[2])))
+ return result
+
+
+def parse_rounds(value: str) -> tuple[int | None, bool | None]:
+ for pattern in (EN_ROUNDS_VALUE_RE, ZH_ROUNDS_VALUE_RE):
+ if match := pattern.fullmatch(value):
+ return int(match.group("rounds")), match.group("positive") is not
None
+ return None, None
+
+
+def parse_document(path: Path, errors: list[str]) -> Document:
+ document = Document(path=path)
+ current_finding: str | None = None
+ in_fence = False
+
+ for lineno, line in
enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
+ if CODE_FENCE_RE.match(line):
+ in_fence = not in_fence
+ continue
+ if in_fence:
+ continue
+
+ if match := HEAD_RE.match(line):
+ if document.head_sha is not None:
+ errors.append(f"{path}:{lineno}: duplicate PR head row")
+ document.head_sha = match.group(1).lower()
+
+ if match := VERDICT_RE.match(line):
+ if document.verdict is not None:
+ errors.append(f"{path}:{lineno}: duplicate verdict row")
+ document.verdict = match.group(1)
+
+ if match := ROUNDS_RE.match(line):
+ if document.rounds is not None:
+ errors.append(f"{path}:{lineno}: duplicate rounds row")
+ document.rounds, document.converged = parse_rounds(match.group(1))
+
+ if match := FINDING_RE.match(line):
+ current_finding = match.group(1)
+ if current_finding in document.finding_ids:
+ errors.append(f"{path}:{lineno}: duplicate finding
{current_finding}")
+ else:
+ document.finding_ids.append(current_finding)
+
+ if match := SEVERITY_RE.match(line):
+ if current_finding is None:
+ errors.append(f"{path}:{lineno}: severity is not under a
finding")
+ elif current_finding in document.severities:
+ errors.append(f"{path}:{lineno}: duplicate severity for
{current_finding}")
+ else:
+ document.severities[current_finding] = match.group(1).title()
+
+ for match in ANCHOR_RE.finditer(line):
+ anchor = Anchor(
+ path=match.group(1),
+ start=int(match.group(2)),
+ end=int(match.group(3)) if match.group(3) else None,
+ source_line=lineno,
+ )
+ document.anchors.append(anchor)
+ if current_finding is not None:
+ document.finding_anchors[current_finding].append(anchor)
+
+ if document.head_sha is None:
+ errors.append(f"{path}: missing PR head row")
+ if document.verdict is None:
+ errors.append(f"{path}: missing Verdict/结论 row")
+ if document.rounds is None:
+ errors.append(f"{path}: missing Rounds/轮次 value")
+ elif not 1 <= document.rounds <= 3:
+ errors.append(f"{path}: rounds must be between 1 and 3")
+ if document.converged is None:
+ errors.append(f"{path}: rounds row must state convergence")
+
+ for finding in document.finding_ids:
+ if finding not in document.severities:
+ errors.append(f"{path}: finding {finding} has no severity")
+ if not document.finding_anchors.get(finding):
+ errors.append(f"{path}: finding {finding} has no `path:line`
anchor")
+
+ counts = severity_counts(document)
+ expected = "REQUEST_CHANGES" if counts["blocker"] or counts["major"] else
"APPROVE"
+ if document.verdict is not None and document.verdict != expected:
+ errors.append(f"{path}: verdict {document.verdict} conflicts with
finding severities")
+ return document
+
+
+def severity_counts(document: Document) -> dict[str, int]:
+ return {
+ name.casefold(): sum(1 for value in document.severities.values() if
value == name)
+ for name in SEVERITIES
+ }
+
+
+def finding_anchor_keys(document: Document, finding: str) -> list[tuple[str,
int, int]]:
+ """Return stable semantic anchors, excluding document source-line
metadata."""
+ return sorted(
+ {
+ (anchor.path, anchor.start, anchor.end or anchor.start)
+ for anchor in document.finding_anchors.get(finding, [])
+ }
+ )
+
+
+def verify_anchors(
+ document: Document,
+ repo_root: Path,
+ changed_ranges: dict[str, list[tuple[int, int]]],
+ errors: list[str],
+ warnings: list[str],
+) -> None:
+ line_counts: dict[str, int] = {}
+ for anchor in document.anchors:
+ if anchor.end is not None and anchor.end < anchor.start:
+ errors.append(
+ f"{document.path}:{anchor.source_line}:
{anchor.path}:{anchor.start}-{anchor.end} "
+ "has an end before its start"
+ )
+ continue
+ target = repo_root / anchor.path
+ if not target.is_file():
+ errors.append(f"{document.path}:{anchor.source_line}: anchor path
does not exist -> {anchor.path}")
+ continue
+ if anchor.path not in line_counts:
+ try:
+ line_counts[anchor.path] = sum(
+ 1 for _ in target.open(encoding="utf-8", errors="replace")
+ )
+ except OSError as exc:
+ errors.append(
+ f"{document.path}:{anchor.source_line}: cannot read
{anchor.path}: {exc}"
+ )
+ continue
+ last = anchor.end or anchor.start
+ if anchor.start < 1 or last > line_counts[anchor.path]:
+ errors.append(
+ f"{document.path}:{anchor.source_line}:
{anchor.path}:{anchor.start} out of range "
+ f"(file has {line_counts[anchor.path]} lines)"
+ )
+ continue
+ ranges = changed_ranges.get(anchor.path)
+ if not ranges:
+ warnings.append(f"{document.path}:{anchor.source_line}:
{anchor.path} is outside the changed-file set")
+ elif not any(not (last < start or anchor.start > end) for start, end
in ranges):
+ warnings.append(f"{document.path}:{anchor.source_line}:
{anchor.path}:{anchor.start} is unchanged context")
+
+
+def main() -> int:
+ args = parse_args()
+ errors: list[str] = []
+ warnings: list[str] = []
+ try:
+ meta = read_meta(args.ctx.resolve())
+ except ValueError as exc:
+ print(f"ERROR: {exc}", file=sys.stderr)
+ return 1
+
+ head_sha = meta.get("HEAD_SHA", "").lower()
+ if not re.fullmatch(r"[0-9a-f]{40}", head_sha):
+ errors.append("meta.env HEAD_SHA is not a full SHA")
+
+ repo_root = (args.repo_root or Path(meta.get("REPO_ROOT", "."))).resolve()
+ docs_root = Path(meta.get("DOCS_ROOT", repo_root)).resolve()
+ pr_number = meta.get("PR_NUMBER", "")
+ expected_paths = {
+ (docs_root / "review-docs" / f"pr-{pr_number}-review.en.md").resolve(),
+ (docs_root / "review-docs" / f"pr-{pr_number}-review.zh.md").resolve(),
+ }
+ actual_paths = {path.resolve() for path in args.doc}
+ if len(args.doc) != 2 or (pr_number and actual_paths != expected_paths):
+ errors.append("use exactly the standard PR-specific EN and ZH review
documents")
+
+ documents: list[Document] = []
+ changed_ranges = load_changed_ranges(args.ctx.resolve())
+ for path in args.doc:
+ resolved = path.resolve()
+ if not resolved.is_file():
+ errors.append(f"{resolved}: document not found")
+ continue
+ document = parse_document(resolved, errors)
+ verify_anchors(document, repo_root, changed_ranges, errors, warnings)
+ documents.append(document)
+
+ if len(documents) == 2:
+ left, right = documents
+ if left.finding_ids != right.finding_ids:
+ errors.append("EN and ZH finding IDs or order differ")
+ if left.severities != right.severities:
+ errors.append("EN and ZH finding severities differ")
+ for finding in sorted(set(left.finding_ids) & set(right.finding_ids)):
+ if finding_anchor_keys(left, finding) !=
finding_anchor_keys(right, finding):
+ errors.append(f"EN and ZH anchors differ for {finding}")
+ if (left.head_sha, left.verdict, left.rounds, left.converged) != (
+ right.head_sha,
+ right.verdict,
+ right.rounds,
+ right.converged,
+ ):
+ errors.append("EN and ZH head, verdict, rounds, or convergence
differ")
+ if left.head_sha != head_sha:
+ errors.append(f"review documents target {left.head_sha}, but
context head is {head_sha}")
+
+ output = sys.stderr if args.json else sys.stdout
+ for warning in warnings:
+ print(f"NOTE: {warning}", file=output)
+ if errors:
+ for error in errors:
+ print(f"ERROR: {error}", file=sys.stderr)
+ return 1
+ if len(documents) != 2:
+ print("ERROR: both review documents are required", file=sys.stderr)
+ return 1
+
+ result = {
+ "commit": head_sha,
+ "verdict": documents[0].verdict,
+ "findings": severity_counts(documents[0]),
+ "rounds": documents[0].rounds,
+ "converged": documents[0].converged,
+ }
+ if args.json:
+ print(json.dumps(result, separators=(",", ":")))
+ else:
+ print("OK: both review documents agree and every anchor resolves.")
+ print(json.dumps(result, indent=2))
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/verify/README.md b/verify/README.md
index 389fa4e..150bbfb 100644
--- a/verify/README.md
+++ b/verify/README.md
@@ -8,6 +8,16 @@ real Apache Doris cluster and against `doriscli`. Three layers:
| **L1 — knowledge** | Every DDL template (T1–T5) and DDL gotcha in
`doris-best-practices/SKILL.md` is accepted / rejected exactly as claimed |
`mysql` client | ✅ `run.sh` |
| **L2 — CLI contract** | Every command + JSON field in `CLI-CONTRACT.md`
really exists in `doriscli` | `doriscli --format json` + `jq` | ✅ `cli/run.sh` |
| **L3 — behavior** | Triggering, evidence-first / safety guardrails,
end-to-end DDL that loops back through L1 | nested `claude -p` | ✅ `behavior/`
(`run.sh` + `e2e-advisor-ddl.sh` + `triggering.sh`) |
+| **Repo review** | Runtime policy, verified review result, commit binding,
and automatic comment behavior | shell + Python + mocked `gh` | ✅
`repo-review/run.sh` |
+
+Run the repository-review contract tests without a cluster or GitHub access:
+
+```bash
+./verify/repo-review/run.sh
+```
+
+The runner keeps policy, runtime attestation, document validation, and mocked
comment posting in
+separate scripts so each contract can be reviewed and run independently.
## Run L1
diff --git a/verify/repo-review/post-comment.sh
b/verify/repo-review/post-comment.sh
new file mode 100755
index 0000000..3c3864f
--- /dev/null
+++ b/verify/repo-review/post-comment.sh
@@ -0,0 +1,149 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+S="$ROOT/skills/doris-repo-review/scripts"
+TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/doris-review-post.XXXXXX")"
+trap 'rm -rf "$TMP_ROOT"' EXIT
+REPO="$TMP_ROOT/repo"
+CTX="$TMP_ROOT/ctx"
+HEAD_SHA="1111111111111111111111111111111111111111"
+OTHER_SHA="3333333333333333333333333333333333333333"
+BASE_SHA="2222222222222222222222222222222222222222"
+PASS_COUNT=0
+mkdir -p "$TMP_ROOT/bin" "$REPO/src" "$REPO/review-docs" "$CTX"
+printf 'int answer = 42;\n' > "$REPO/src/Foo.java"
+printf 'src/Foo.java\t1\t1\n' > "$CTX/changed_line_ranges.tsv"
+
+pass() { PASS_COUNT=$((PASS_COUNT + 1)); echo "PASS: $1"; }
+fail() { echo "FAIL: $1" >&2; exit 1; }
+
+expect_failure() {
+ local label="$1" pattern="$2"
+ shift 2
+ if "$@" >"$TMP_ROOT/out" 2>"$TMP_ROOT/err"; then
+ fail "$label unexpectedly succeeded"
+ fi
+ if ! grep -Fq "$pattern" "$TMP_ROOT/err" && ! grep -Fq "$pattern"
"$TMP_ROOT/out"; then
+ fail "$label failed for the wrong reason"
+ fi
+ pass "$label"
+}
+
+write_meta() {
+ cat > "$CTX/meta.env" <<EOF
+UPSTREAM_REPO=apache/doris
+PR_NUMBER=123
+PR_URL=https://github.com/apache/doris/pull/123
+BASE_SHA=$BASE_SHA
+HEAD_SHA=$1
+REPO_ROOT=$REPO
+DOCS_ROOT=$REPO
+EOF
+}
+
+write_docs() {
+ local head="$1" verdict="$2" severity="$3" rounds="$4" convergence="$5"
+ local zh_convergence=已收敛
+ [ "$convergence" = converged ] || zh_convergence=未收敛
+ cat > "$REPO/review-docs/pr-123-review.en.md" <<EOF
+# Code Review — PR #123: fixture
+| | |
+|---|---|
+| PR head | \`$head\` on \`feature\` of \`fork\` |
+| Verdict | **$verdict** |
+| Rounds | $rounds of max 3, $convergence |
+### F-01 · fixture
+- **Severity**: $severity
+- **Where**: \`src/Foo.java:1\`
+EOF
+ cat > "$REPO/review-docs/pr-123-review.zh.md" <<EOF
+# 代码评审 — PR #123:fixture
+| | |
+|---|---|
+| PR head | \`$head\`,来自 \`fork\` 的 \`feature\` |
+| 结论 | **$verdict** |
+| 轮次 | 共 $rounds 轮(上限 3),$zh_convergence |
+### F-01 · fixture
+- **等级**:$severity
+- **位置**:\`src/Foo.java:1\`
+EOF
+}
+
+cat > "$TMP_ROOT/bin/gh" <<'EOF'
+#!/usr/bin/env bash
+set -euo pipefail
+if [ "$1" = api ] && [ "$2" = repos/apache/doris/pulls/123 ]; then
+ printf '%s\topen\n' "${MOCK_LIVE_HEAD:?}"
+elif [ "$1" = api ] && [ "$2" = user ]; then
+ printf 'reviewer-one\n'
+elif [ "$1" = api ] && [ "$2" = repos/apache/doris/issues/123/comments ]; then
+ [ "${MOCK_COMMENT_LIST_FAIL:-0}" = 1 ] && exit 1
+ [ "${MOCK_SAME_COMMENT:-0}" = 1 ] && printf '987\treviewer-one\tcommit:
%s\n' "${MOCK_LIVE_HEAD:?}"
+ exit 0
+elif [ "$1" = api ] && [ "$2" = --method ]; then
+ printf '%s\n' "$*" >> "${MOCK_GH_LOG:?}"
+ cat > "${MOCK_GH_BODY:?}"
+ printf
'{"html_url":"https://github.com/apache/doris/pull/123#issuecomment-test"}\n'
+else
+ echo "unexpected gh invocation: $*" >&2
+ exit 1
+fi
+EOF
+chmod +x "$TMP_ROOT/bin/gh"
+export PATH="$TMP_ROOT/bin:$PATH"
+export MOCK_GH_LOG="$TMP_ROOT/gh.log"
+export MOCK_GH_BODY="$TMP_ROOT/body.json"
+export MOCK_LIVE_HEAD="$HEAD_SHA"
+
+write_meta "$HEAD_SHA"
+write_docs "$HEAD_SHA" APPROVE Minor 2 converged
+expect_failure "poster requires runtime attestation" "review-runtime.json not
found" \
+ "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
+"$S/record-review-runtime.sh" --ctx "$CTX" --model gpt-5.6-sol --effort xhigh
>/dev/null
+
+"$S/post-pass-comment.sh" --ctx "$CTX" --dry-run > "$TMP_ROOT/dry-run"
+grep -Fq "commit: $HEAD_SHA" "$CTX/pr-comment.md" || fail "receipt commit is
wrong"
+grep -Fq "model: gpt-5.6-sol" "$CTX/pr-comment.md" || fail "receipt model is
wrong"
+grep -Fq "findings: {blocker: 0, major: 0, minor: 1, nit: 0}"
"$CTX/pr-comment.md" \
+ || fail "receipt findings are wrong"
+[ -z "${RECEIPT_OUTPUT:-}" ] || cp "$CTX/pr-comment.md" "$RECEIPT_OUTPUT"
+pass "verified dry run renders a pipeline-compatible receipt"
+
+: > "$MOCK_GH_LOG"
+MOCK_SAME_COMMENT=0 "$S/post-pass-comment.sh" --ctx "$CTX" > "$TMP_ROOT/create"
+grep -Fq -- "--method POST repos/apache/doris/issues/123/comments --input -"
"$MOCK_GH_LOG" \
+ || fail "poster did not create a comment"
+pass "qualified PASS creates a comment"
+
+: > "$MOCK_GH_LOG"
+MOCK_SAME_COMMENT=1 "$S/post-pass-comment.sh" --ctx "$CTX" > "$TMP_ROOT/update"
+grep -Fq -- "--method PATCH repos/apache/doris/issues/comments/987 --input -"
"$MOCK_GH_LOG" \
+ || fail "poster did not update the same-commit comment"
+pass "same-commit rerun updates the comment"
+
+MOCK_COMMENT_LIST_FAIL=1 expect_failure "comment lookup failure stops posting"
\
+ "cannot list existing review comments" "$S/post-pass-comment.sh" --ctx
"$CTX" --dry-run
+
+BAD_NOTES="$TMP_ROOT/bad-notes.md"
+printf 'not a bullet' > "$BAD_NOTES"
+expect_failure "unterminated malformed note is rejected" "every note line" \
+ "$S/post-pass-comment.sh" --ctx "$CTX" --notes-file "$BAD_NOTES" --dry-run
+
+write_docs "$HEAD_SHA" REQUEST_CHANGES Major 2 converged
+expect_failure "REQUEST_CHANGES never posts" "verdict is REQUEST_CHANGES" \
+ "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
+
+write_docs "$HEAD_SHA" APPROVE Minor 3 'did not converge'
+expect_failure "non-converged review never posts" "did not converge" \
+ "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
+
+write_docs "$HEAD_SHA" APPROVE Minor 2 converged
+MOCK_LIVE_HEAD="$OTHER_SHA" expect_failure "moved PR head invalidates review"
"PR head moved" \
+ "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
+
+write_meta "$OTHER_SHA"
+MOCK_LIVE_HEAD="$OTHER_SHA" expect_failure "runtime attestation is
commit-bound" "was recorded for" \
+ "$S/post-pass-comment.sh" --ctx "$CTX" --dry-run
+
+echo "$PASS_COUNT post-comment tests passed"
diff --git a/verify/repo-review/review-documents.sh
b/verify/repo-review/review-documents.sh
new file mode 100755
index 0000000..1544774
--- /dev/null
+++ b/verify/repo-review/review-documents.sh
@@ -0,0 +1,144 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+VERIFIER="$ROOT/skills/doris-repo-review/scripts/verify-review-docs.py"
+TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/doris-review-docs.XXXXXX")"
+trap 'rm -rf "$TMP_ROOT"' EXIT
+REPO="$TMP_ROOT/repo"
+CTX="$TMP_ROOT/ctx"
+HEAD_SHA="1111111111111111111111111111111111111111"
+OTHER_SHA="3333333333333333333333333333333333333333"
+PASS_COUNT=0
+mkdir -p "$REPO/src" "$REPO/review-docs" "$CTX"
+printf 'int answer = 42;\nint other = 7;\n' > "$REPO/src/Foo.java"
+printf 'src/Foo.java\t1\t2\n' > "$CTX/changed_line_ranges.tsv"
+
+pass() { PASS_COUNT=$((PASS_COUNT + 1)); echo "PASS: $1"; }
+fail() { echo "FAIL: $1" >&2; exit 1; }
+
+expect_failure() {
+ local label="$1" pattern="$2"
+ shift 2
+ if "$@" >"$TMP_ROOT/out" 2>"$TMP_ROOT/err"; then
+ fail "$label unexpectedly succeeded"
+ fi
+ grep -Fq "$pattern" "$TMP_ROOT/err" || fail "$label failed for the wrong
reason"
+ pass "$label"
+}
+
+write_meta() {
+ cat > "$CTX/meta.env" <<EOF
+PR_NUMBER=123
+HEAD_SHA=$1
+REPO_ROOT=$REPO
+DOCS_ROOT=$REPO
+EOF
+}
+
+write_docs() {
+ local head="$1" verdict="$2" en_severity="$3" zh_severity="$4"
+ local rounds="$5" en_convergence="$6" zh_convergence="$7"
+ local en_anchor="${8:-1}"
+ local zh_anchor="${9:-$en_anchor}"
+ local en_max="${10:-3}"
+ local zh_max="${11:-$en_max}"
+ cat > "$REPO/review-docs/pr-123-review.en.md" <<EOF
+# Code Review — PR #123: fixture
+
+| | |
+|---|---|
+| PR head | \`$head\` on \`feature\` of \`fork\` |
+| Verdict | **$verdict** |
+| Rounds | $rounds of max $en_max; $en_convergence |
+
+## Findings
+
+### F-01 · fixture finding
+
+- **Severity**: $en_severity
+- **Where**: \`src/Foo.java:$en_anchor\`
+EOF
+ cat > "$REPO/review-docs/pr-123-review.zh.md" <<EOF
+# 代码评审 — PR #123:fixture
+
+| | |
+|---|---|
+| PR head | \`$head\`,来自 \`fork\` 的 \`feature\` |
+| 结论 | **$verdict** |
+| 轮次 | 共 ${rounds} 轮(上限 ${zh_max});${zh_convergence} |
+
+## 问题清单
+
+### F-01 · fixture finding
+
+- **等级**:$zh_severity
+- **位置**:\`src/Foo.java:$zh_anchor\`
+EOF
+}
+
+verify_json() {
+ "$VERIFIER" --json --ctx "$CTX" \
+ --doc "$REPO/review-docs/pr-123-review.en.md" \
+ --doc "$REPO/review-docs/pr-123-review.zh.md"
+}
+
+write_meta "$HEAD_SHA"
+write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛
+RESULT="$(verify_json)"
+jq -e --arg head "$HEAD_SHA" \
+ '.commit == $head and .verdict == "APPROVE" and .findings.minor == 1 and
.rounds == 2 and .converged' \
+ <<<"$RESULT" >/dev/null || fail "verified result JSON is wrong"
+pass "matching documents produce one verified result"
+
+write_docs "$OTHER_SHA" APPROVE Minor Minor 2 converged 已收敛
+expect_failure "document commit must match context" "context head" verify_json
+
+write_docs "$HEAD_SHA" APPROVE Minor Nit 2 converged 已收敛
+expect_failure "EN and ZH severities must agree" "severities differ"
verify_json
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛 1 2
+expect_failure "EN and ZH anchors must agree" "anchors differ" verify_json
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 4 converged 已收敛
+expect_failure "rounds must stay within the cap" "between 1 and 3" verify_json
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛 3
+expect_failure "anchors must resolve" "out of range" verify_json
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 2 converged 已收敛 2-1
+expect_failure "anchor ranges cannot run backwards" "end before its start"
verify_json
+
+write_docs "$HEAD_SHA" REQUEST_CHANGES Major Major 2 converged 已收敛
+RESULT="$(verify_json)"
+jq -e '.verdict == "REQUEST_CHANGES" and .findings.major == 1' <<<"$RESULT"
>/dev/null \
+ || fail "REQUEST_CHANGES result is wrong"
+pass "Major findings produce REQUEST_CHANGES"
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 3 'did not converge' 未收敛
+RESULT="$(verify_json)"
+jq -e '.converged == false' <<<"$RESULT" >/dev/null || fail "non-convergence
was not preserved"
+pass "documented non-converged result remains explicit"
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 2 \
+ 'converged (verdict stable since round 1)' '已收敛(结论自第 1 轮起稳定)'
+RESULT="$(verify_json)"
+jq -e '.rounds == 2 and .converged == true' <<<"$RESULT" >/dev/null \
+ || fail "annotated convergence was not preserved"
+pass "documented convergence notes are accepted"
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 3 unconverged 已收敛
+expect_failure "convergence substring is rejected" "missing Rounds/轮次 value"
verify_json
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 3 not-converged 已收敛
+expect_failure "hyphenated negative is not certified as converged" \
+ "missing Rounds/轮次 value" verify_json
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 3 converged '尚未达到已收敛状态'
+expect_failure "Chinese negative prose is not certified as converged" \
+ "missing Rounds/轮次 value" verify_json
+
+write_docs "$HEAD_SHA" APPROVE Minor Minor 3 converged 已收敛 1 1 30 30
+expect_failure "malformed maximum is rejected" "missing Rounds/轮次 value"
verify_json
+
+echo "$PASS_COUNT review-document tests passed"
diff --git a/verify/repo-review/run.sh b/verify/repo-review/run.sh
new file mode 100755
index 0000000..9f55b8b
--- /dev/null
+++ b/verify/repo-review/run.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+DIR="$(cd "$(dirname "$0")" && pwd)"
+for test in runtime-policy runtime-attestation review-documents post-comment;
do
+ "$DIR/$test.sh"
+done
diff --git a/verify/repo-review/runtime-attestation.sh
b/verify/repo-review/runtime-attestation.sh
new file mode 100755
index 0000000..52551b4
--- /dev/null
+++ b/verify/repo-review/runtime-attestation.sh
@@ -0,0 +1,59 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+RECORDER="$ROOT/skills/doris-repo-review/scripts/record-review-runtime.sh"
+TMP_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/doris-review-runtime.XXXXXX")"
+trap 'rm -rf "$TMP_ROOT"' EXIT
+HEAD_SHA="1111111111111111111111111111111111111111"
+PASS_COUNT=0
+
+pass() { PASS_COUNT=$((PASS_COUNT + 1)); echo "PASS: $1"; }
+fail() { echo "FAIL: $1" >&2; exit 1; }
+
+expect_failure() {
+ local label="$1" pattern="$2"
+ shift 2
+ if "$@" >"$TMP_ROOT/out" 2>"$TMP_ROOT/err"; then
+ fail "$label unexpectedly succeeded"
+ fi
+ grep -Fq "$pattern" "$TMP_ROOT/err" || fail "$label failed for the wrong
reason"
+ pass "$label"
+}
+
+write_meta() {
+ mkdir -p "$1"
+ printf 'HEAD_SHA=%s\n' "$2" > "$1/meta.env"
+}
+
+CTX="$TMP_ROOT/ctx"
+write_meta "$CTX" "$HEAD_SHA"
+"$RECORDER" --ctx "$CTX" --model gpt-5.6-sol --effort xhigh >/dev/null
+jq -e --arg head "$HEAD_SHA" \
+ '.model == "gpt-5.6-sol" and .effort == "xhigh" and .commit == $head' \
+ "$CTX/review-runtime.json" >/dev/null || fail "runtime JSON fields are
wrong"
+pass "qualified runtime records model, effort, and commit"
+
+expect_failure "low effort is rejected" "not eligible for model 'gpt-5.6-sol'"
\
+ "$RECORDER" --ctx "$CTX" --model gpt-5.6-sol --effort high
+[ ! -e "$CTX/review-runtime.json" ] || fail "failed replacement left an old
runtime record"
+pass "failed replacement removes the old attestation"
+
+expect_failure "Claude ultra effort is rejected" "not eligible for model
'claude-opus-5'" \
+ "$RECORDER" --ctx "$CTX" --model claude-opus-5 --effort ultra
+
+"$RECORDER" --ctx "$CTX" --model claude-opus-5 --effort max >/dev/null
+jq -e --arg head "$HEAD_SHA" \
+ '.model == "claude-opus-5" and .effort == "max" and .commit == $head' \
+ "$CTX/review-runtime.json" >/dev/null || fail "supported Claude runtime
was not recorded"
+pass "supported Claude effort is recorded"
+
+expect_failure "unlisted model is rejected" "is not eligible" \
+ "$RECORDER" --ctx "$CTX" --model gpt-5.6-terra --effort xhigh
+
+BAD_CTX="$TMP_ROOT/bad-ctx"
+write_meta "$BAD_CTX" deadbeef
+expect_failure "short commit is rejected" "not a full SHA" \
+ "$RECORDER" --ctx "$BAD_CTX" --model gpt-5.6-sol --effort xhigh
+
+echo "$PASS_COUNT runtime-attestation tests passed"
diff --git a/verify/repo-review/runtime-policy.sh
b/verify/repo-review/runtime-policy.sh
new file mode 100755
index 0000000..83fbe01
--- /dev/null
+++ b/verify/repo-review/runtime-policy.sh
@@ -0,0 +1,72 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+POLICY="$ROOT/skills/doris-repo-review/scripts/review-runtime-policy.sh"
+PASS_COUNT=0
+
+pass() {
+ PASS_COUNT=$((PASS_COUNT + 1))
+ echo "PASS: $1"
+}
+
+fail() {
+ echo "FAIL: $1" >&2
+ exit 1
+}
+
+# shellcheck source=/dev/null
+. "$POLICY"
+
+for model in claude-opus-5 'claude-opus-5[1m]' claude-fable-5
'claude-fable-5[1m]' gpt-5.6-sol; do
+ is_eligible_review_model "$model" || fail "eligible model rejected: $model"
+done
+pass "exact model allowlist is accepted"
+
+for model in claude-opus-5.1 claude-fable-4 gpt-5.6-terra gpt-5.7-sol unknown;
do
+ if is_eligible_review_model "$model"; then
+ fail "unlisted model accepted: $model"
+ fi
+done
+pass "unlisted models are rejected"
+
+for model in claude-opus-5 'claude-opus-5[1m]' claude-fable-5
'claude-fable-5[1m]'; do
+ for effort in xhigh max; do
+ is_eligible_review_effort "$model" "$effort" \
+ || fail "eligible Claude runtime rejected: $model ($effort)"
+ done
+done
+pass "Claude models accept their supported qualified efforts"
+
+for model in claude-opus-5 'claude-opus-5[1m]' claude-fable-5
'claude-fable-5[1m]'; do
+ if is_eligible_review_effort "$model" ultra; then
+ fail "unsupported Claude ultra effort accepted: $model"
+ fi
+done
+pass "Claude models reject unsupported ultra effort"
+
+for effort in xhigh max ultra; do
+ is_eligible_review_effort gpt-5.6-sol "$effort" \
+ || fail "eligible Codex runtime rejected: gpt-5.6-sol ($effort)"
+done
+pass "Codex accepts xhigh, max, and ultra"
+
+for model in claude-opus-5 gpt-5.6-sol; do
+ for effort in minimal low medium high unknown; do
+ if is_eligible_review_effort "$model" "$effort"; then
+ fail "ineligible runtime accepted: $model ($effort)"
+ fi
+ done
+done
+pass "lower and unknown efforts are rejected for every host"
+
+"$POLICY" check gpt-5.6-sol xhigh >/dev/null
+if "$POLICY" check gpt-5.6-sol high >/dev/null 2>&1; then
+ fail "policy CLI accepted high effort"
+fi
+if "$POLICY" check claude-opus-5 ultra >/dev/null 2>&1; then
+ fail "policy CLI accepted unsupported Claude ultra effort"
+fi
+pass "policy CLI enforces model-specific effort allowlists"
+
+echo "$PASS_COUNT runtime-policy tests passed"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]