This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch codex/report-review-runtime-effort
in repository https://gitbox.apache.org/repos/asf/doris-skills.git
The following commit(s) were added to
refs/heads/codex/report-review-runtime-effort by this push:
new e16aa6f fix: publish verified local review receipts (#9)
e16aa6f is described below
commit e16aa6fe1d7363d0462c85269e9fc904afef39ed
Author: shuke <[email protected]>
AuthorDate: Tue Sep 1 22:54:45 2026 +0800
fix: publish verified local review receipts (#9)
* fix: publish verified local review receipts
* fix: harden verified review receipts
---
README.md | 11 +-
skills/doris-repo-review/SKILL.md | 88 +++---
.../doris-repo-review/references/doc-templates.md | 2 +-
.../references/pr-comment-format.md | 25 +-
.../references/qualified-runtime.md | 2 +
.../doris-repo-review/scripts/post-pass-comment.sh | 107 +++----
.../scripts/record-review-runtime.sh | 43 +++
skills/doris-repo-review/scripts/verify-anchors.py | 172 ------------
.../scripts/verify-review-docs.py | 312 +++++++++++++++++++++
verify/README.md | 7 +-
verify/repo-review/post-comment.sh | 149 ++++++++++
verify/repo-review/review-documents.sh | 121 ++++++++
verify/repo-review/run.sh | 5 +-
verify/repo-review/runtime-attestation.sh | 50 ++++
14 files changed, 801 insertions(+), 293 deletions(-)
diff --git a/README.md b/README.md
index 0ff7565..1ef36b3 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 99ff69c..80d1db2 100644
--- a/skills/doris-repo-review/SKILL.md
+++ b/skills/doris-repo-review/SKILL.md
@@ -1,6 +1,6 @@
---
name: doris-repo-review
-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, [...]
+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
@@ -34,8 +34,9 @@ Let `$S` be this skill's own `scripts/` directory (with a
default Claude Code in
$S/align-to-pr.sh <PR> --check # diagnosis only, changes
nothing
$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
-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
```
@@ -43,7 +44,8 @@ $S/review-runtime-policy.sh check <model> <effort>
# reviewer
|---|---|
| `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/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 |
| `references/prompts.md` | Subagent prompt templates (CI wording, carried
over verbatim) |
@@ -72,9 +74,9 @@ pipeline-equivalent PASS comment.
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
@@ -90,7 +92,7 @@ pipeline-equivalent PASS comment.
"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.
---
@@ -169,6 +171,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.
+
---
## 3. Required reading (in this order)
@@ -318,23 +329,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.
@@ -343,32 +353,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.
---
@@ -381,7 +373,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.
@@ -404,7 +396,7 @@ Tell the user:
| Single-file ledger with sections | A `ledger/` directory, one file per owner
|
| Main risk scan → 1-3 full-review subagents + risk-focused → merge → ≤3
rounds | Identical |
| `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 |
---
@@ -433,13 +425,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 c1a1c1d..9c9c394 100644
--- a/skills/doris-repo-review/references/doc-templates.md
+++ b/skills/doris-repo-review/references/doc-templates.md
@@ -17,7 +17,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..b099fe7 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` | `xhigh`, `max`, or `ultra` | `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
index 1054392..ad9cf5c 100644
--- a/skills/doris-repo-review/references/qualified-runtime.md
+++ b/skills/doris-repo-review/references/qualified-runtime.md
@@ -13,6 +13,8 @@ Validate the exact values with:
$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.
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/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..ae6a660
--- /dev/null
+++ b/skills/doris-repo-review/scripts/verify-review-docs.py
@@ -0,0 +1,312 @@
+#!/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*$")
+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]:
+ number = re.match(r"\s*(?:共\s*)?(\d+)(?:\s+of max 3|\s*轮)", value)
+ lowered = value.casefold()
+ if "not converged" in lowered or "未收敛" in value:
+ converged = False
+ elif "converged" in lowered or "已收敛" in value:
+ converged = True
+ else:
+ converged = None
+ return (int(number.group(1)) if number else None, converged)
+
+
+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 c3d4530..150bbfb 100644
--- a/verify/README.md
+++ b/verify/README.md
@@ -8,14 +8,17 @@ 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** | Exact model and effort policy for pipeline-equivalent
local reviews | shell | ✅ `repo-review/run.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 policy tests without a cluster or GitHub access:
+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
```bash
diff --git a/verify/repo-review/post-comment.sh
b/verify/repo-review/post-comment.sh
new file mode 100755
index 0000000..45b0149
--- /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 'not converged'
+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..4faf4b8
--- /dev/null
+++ b/verify/repo-review/review-documents.sh
@@ -0,0 +1,121 @@
+#!/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}"
+ 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, $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 轮(上限 3),$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 'not converged' 未收敛
+RESULT="$(verify_json)"
+jq -e '.converged == false' <<<"$RESULT" >/dev/null || fail "non-convergence
was not preserved"
+pass "non-converged result remains explicit"
+
+echo "$PASS_COUNT review-document tests passed"
diff --git a/verify/repo-review/run.sh b/verify/repo-review/run.sh
index e9f8ab3..9f55b8b 100755
--- a/verify/repo-review/run.sh
+++ b/verify/repo-review/run.sh
@@ -1,4 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
-"$(cd "$(dirname "$0")" && pwd)/runtime-policy.sh"
+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..6e0ccd3
--- /dev/null
+++ b/verify/repo-review/runtime-attestation.sh
@@ -0,0 +1,50 @@
+#!/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" "use xhigh or higher" \
+ "$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 "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"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]