This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 45790220bd4 Use a cumulative findings ledger for PR review convergence
(#40307)
45790220bd4 is described below
commit 45790220bd444969d20d7dc97e2e3b49c56517fd
Author: Liang Zhang <[email protected]>
AuthorDate: Wed Oct 21 16:08:07 2026 +0800
Use a cumulative findings ledger for PR review convergence (#40307)
* Limit PR review confirmation to six independent assessments
* Use a cumulative findings ledger for PR review convergence
* Fix review ledger evidence freshness and historical stage validation
---
.codex/harness/agents/cases.toml | 56 ++---
.codex/skills/review-pr/SKILL.md | 7 +-
.../review-pr/references/review-coordination.md | 51 ++--
.codex/skills/review-pr/scripts/review_ledger.py | 140 ++++++-----
.../skills/review-pr/tests/test_review_ledger.py | 271 +++++++++++++++++----
5 files changed, 355 insertions(+), 170 deletions(-)
diff --git a/.codex/harness/agents/cases.toml b/.codex/harness/agents/cases.toml
index 302248e3946..10178e95912 100644
--- a/.codex/harness/agents/cases.toml
+++ b/.codex/harness/agents/cases.toml
@@ -3835,7 +3835,7 @@ required_summary_terms = ["A=change-request",
"B=change-request", "C=mergeable",
forbidden_summary_terms = ["A=continue-review", "A=mergeable",
"A=review-incomplete", "A=needs-discussion", "B=continue-review",
"B=mergeable", "B=review-incomplete", "B=needs-discussion",
"C=continue-review", "C=review-incomplete", "C=change-request",
"C=needs-discussion", "D=continue-review", "D=review-incomplete",
"D=change-request", "D=needs-discussion"]
critical = true
policy_binding_profile = "review-pr"
-policy_assertions = [{source = "review-pr", statements = ["Record each
applicable requirement's source, content version, scope and valid exceptions in
readable basis source material; a task-only requirement applies only to its
authorized task candidate and must not become a universal repository or
public-PR rule.", "Keep effective content and delta identity separate from Git
revision lineage so equal local and public candidates retain one
code-correctness basis.", "Two reviews with the s [...]
+policy_assertions = [{source = "review-pr", statements = ["Record each
applicable requirement's source, content version, scope and valid exceptions in
readable basis source material; a task-only requirement applies only to its
authorized task candidate and must not become a universal repository or
public-PR rule.", "Keep effective content and delta identity separate from Git
revision lineage so equal local and public candidates retain one
code-correctness basis.", "Completed coordinated [...]
[[cases]]
id = "unchanged_candidate_corrects_proven_prior_review_miss"
@@ -3845,7 +3845,7 @@ phase = "read-only review-correction assessment"
ordinary_loop = false
profile = "review-pr"
semantic_policy_assertions = true
-prompt = "Answer this self-contained read-only review-correction question
using only the supplied facts and bound rules; do not inspect files, run
checks, edit anything or produce a Formal Review report. An earlier
complete-looking Formal Review returned Mergeable. The current effective
candidate content and delta, original baseline, applicable requirements and
exceptions, Code Correctness focus and admissible evidence are identical. No
commit, repair, new requirement or external fact in [...]
+prompt = "Answer this self-contained read-only review-correction question
using only the supplied facts and bound rules; do not inspect files, run
checks, edit anything or produce a Formal Review report. An earlier
complete-looking Formal Review returned Mergeable. The current effective
candidate content and delta, original baseline, applicable requirements and
exceptions, Code Correctness focus and admissible evidence are identical. No
commit, repair, new requirement or external fact in [...]
decision = "proceed"
required_actions = []
allowed_actions = []
@@ -3855,7 +3855,7 @@ required_summary_terms = ["origin=previous-review-miss",
"current=change-request
forbidden_summary_terms = ["origin=introduced-by-latest-commits",
"origin=exposed-by-previous-fix", "origin=changed-review-basis",
"current=continue-review", "current=mergeable", "current=review-incomplete",
"current=needs-discussion", "prior-verdict=evidence"]
critical = true
policy_binding_profile = "review-pr"
-policy_assertions = [{source = "review-pr-coordination", statements = ["Give
every fresh pair the current requirements and their provenance, complete
candidate inventory and relevant raw evidence without the author's full session
history, earlier assessments, candidate lists or dispositions, a target
conclusion or an expected finding count.", "Before changing an overall
conclusion, compare the prior and current effective candidates, requirement
sources and versions, applicable exceptions [...]
+policy_assertions = [{source = "review-pr-coordination", statements = ["Give
every independent reviewer the current requirements and their provenance,
complete candidate inventory and relevant raw evidence without the author's
full session history, earlier assessments, candidate lists or dispositions, a
target conclusion or an expected finding count.", "Before changing an overall
conclusion, compare the prior and current effective candidates, requirement
sources and versions, applicable [...]
[[cases]]
id = "review_ledger_reuses_facts_without_inheriting_completion"
@@ -4421,7 +4421,7 @@ required_summary_term_groups = [["unrelated_rows",
"query"]]
forbidden_summary_terms = ["A.status=keep", "B.status=change",
"note.origin=confirmed-requirement", "query.status=keep", "packet=Q",
"A.unrequired=[]", "B.unrequired=[FormatHook,Formatter.hooks]"]
critical = true
policy_binding_profile = "review-pr"
-policy_assertions = [{source = "review-pr", statements = ["Do not promote an
unverified inference into a requirement for a new interface, SPI, behavior, or
test.", "Reconstruct those requirements from original user requests and
maintained contracts before checking the author's proposed solution,
distinguishing author choices and unverified assumptions from confirmed
constraints."]}, {source = "review-pr-coordination", statements = ["Give every
fresh pair the current requirements and thei [...]
+policy_assertions = [{source = "review-pr", statements = ["Do not promote an
unverified inference into a requirement for a new interface, SPI, behavior, or
test.", "Reconstruct those requirements from original user requests and
maintained contracts before checking the author's proposed solution,
distinguishing author choices and unverified assumptions from confirmed
constraints."]}, {source = "review-pr-coordination", statements = ["Give every
independent reviewer the current requirement [...]
[[cases]]
id = "review_pair_isolation_and_single_pass_roles"
@@ -4441,7 +4441,7 @@ required_summary_terms =
["A=two-independent-assessments", "B=one-complete-workf
forbidden_summary_terms = ["A=one-assessment", "B=start-another-pair",
"packet=Q", "C=repeat-verification", "D=inherit-prior-completion",
"E=claim-two-independent-reviews", "F=skip-pair-for-reply-mode",
"G=expand-to-full-candidate-pair"]
critical = true
policy_binding_profile = "review-pr"
-policy_assertions = [{source = "review-pr-coordination", statements = ["For
every top-level Formal Review and every discussion reply that makes or changes
an overall readiness conclusion, a coordinator must obtain two independent
complete assessments of the same effective candidate, original baseline,
authoritative scope, requirements and rule versions, and review focus.", "A
reply limited to one finding remains scoped to that finding and does not
require a full-candidate pair unless it [...]
+policy_assertions = [{source = "review-pr-coordination", statements = ["For
every top-level Formal Review and every discussion reply that makes or changes
an overall readiness conclusion, a coordinator must first obtain two mutually
blind independent complete assessments of the same effective candidate,
original baseline, authoritative scope, requirements and rule versions, and
review focus.", "A reply limited to one finding remains scoped to that finding
and does not require a full-cand [...]
[[cases]]
id = "matching_review_pair_requires_substantive_result_review"
@@ -4451,97 +4451,97 @@ phase = "read-only review-result evidence assessment"
ordinary_loop = false
profile = "review-pr"
semantic_policy_assertions = true
-prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. A: two isolated complete reports
both say Mergeable and list no findings. The coordinator has not checked their
evidence. Choose A=review-both-results or A=finish-immediately. B: during that
result review the complete current source and supported consuming path prove
that both reports missed a ca [...]
+prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. A: two isolated complete reports
both say Mergeable and list no findings. The coordinator has not checked their
evidence. Choose A=review-both-results or A=finish-immediately. B: during that
result review the complete current source and supported consuming path prove
that both reports missed a ca [...]
decision = "proceed"
required_actions = []
allowed_actions = []
forbidden_actions = ["inspect_local", "edit_code", "edit_non_code",
"run_local_checks", "run_pre_handoff_review", "propose_commit_message",
"mutate_git", "mutate_remote"]
required_reasons = ["read_only_request"]
-required_summary_terms = ["A=review-both-results",
"B=adjudicate-and-obtain-second-pair", "C=different-issue-sets",
"D=same-normalized-issue-set", "E=retain-confirmed-decision-condition"]
-forbidden_summary_terms = ["A=finish-immediately",
"B=accept-matching-mergeable", "C=same-issue-set", "D=different-issue-sets",
"E=invent-code-defect", "E=mergeable"]
+required_summary_terms = ["A=review-both-results",
"B=adjudicate-and-complete", "C=different-issue-sets",
"D=same-normalized-issue-set", "E=retain-confirmed-decision-condition"]
+forbidden_summary_terms = ["A=finish-immediately", "B=obtain-second-pair",
"B=accept-matching-mergeable", "C=same-issue-set", "D=different-issue-sets",
"E=invent-code-defect", "E=mergeable"]
critical = true
policy_binding_profile = "review-pr"
-policy_assertions = [{source = "review-pr-coordination", statements = ["The
coordinator must substantively review both assessments and their supporting
evidence even when their verdicts and issue lists already match.", "Recheck
each claim's full path, strongest counter-evidence, necessity, scope and
decision classification, and check the pair for duplicated issues, omitted
historical candidates and unsupported acceptance conclusions.", "Normalize
issues by root cause, trigger and indepen [...]
+policy_assertions = [{source = "review-pr-coordination", statements = ["The
coordinator must substantively review every assessment and its supporting
evidence even when the verdicts and issue lists already match.", "Recheck each
claim's full path, strongest counter-evidence, necessity, scope and decision
classification, and check the cumulative reports for duplicated issues, omitted
historical candidates and unsupported acceptance conclusions.", "Normalize
issues by root cause, trigger a [...]
[[cases]]
id = "review_pair_preserves_historical_candidate_dispositions"
group = "review-pr independent review coordination"
-description = "Rejects agreement that omits a proven historical issue and
permits evidence-based withdrawal or deduplication."
+description = "Preserves every historical candidate in the verified overall
ledger despite later omissions and permits evidence-based withdrawal or
deduplication."
phase = "read-only historical-review-candidate assessment"
ordinary_loop = false
profile = "review-pr"
semantic_policy_assertions = true
-prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. The basis is unchanged. The first
pair reports respectively issues {A} and {A,B}; a second pair independently
reports {A} and {A}. Every original report is preserved. Scenario P: current
full-path evidence and strongest counter-evidence prove that B is a separate
candidate-introduced defect requi [...]
+prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. The basis is unchanged. The initial
two reports respectively list issues {A} and {A,B}; the next single independent
report lists only {A}. Every original report is preserved, and complete
current-basis coverage and required verification are established. Scenario P:
current full-path evidence and [...]
decision = "proceed"
required_actions = []
allowed_actions = []
forbidden_actions = ["inspect_local", "edit_code", "edit_non_code",
"run_local_checks", "run_pre_handoff_review", "propose_commit_message",
"mutate_git", "mutate_remote"]
required_reasons = ["read_only_request"]
-required_summary_terms = ["P=continue-fresh-review", "Q=accept-latest-pair",
"R=merge-duplicate", "S=reject-mixed-bases"]
-forbidden_summary_terms = ["P=finish-with-A", "P=not-converged",
"Q=require-identical-historical-counts", "R=retain-two-blockers",
"S=reuse-earlier-pair"]
+required_summary_terms = ["P=complete-with-A-and-B",
"Q=complete-with-supported-withdrawal", "R=merge-duplicate",
"S=reject-mixed-bases"]
+forbidden_summary_terms = ["P=continue-fresh-review", "P=finish-with-A",
"P=not-converged", "Q=require-identical-historical-counts",
"R=retain-two-blockers", "S=reuse-earlier-pair"]
critical = true
policy_binding_profile = "review-pr"
-policy_assertions = [{source = "review-pr-coordination", statements =
["Preserve every original assessment and ledger without rewriting them to make
the pair agree.", "Record an evidence-based disposition for every candidate
raised in any assessment, including retained, withdrawn or merged candidates,
with its supporting evidence, strongest counter-evidence and explanation.", "A
later reviewer failing to rediscover a candidate does not withdraw it, and
neither majority voting nor repeate [...]
+policy_assertions = [{source = "review-pr-coordination", statements =
["Preserve every original assessment and ledger without rewriting them to agree
with the overall adjudication.", "Record an evidence-based disposition for
every candidate raised in any assessment, including retained, withdrawn or
merged candidates, with its supporting evidence, strongest counter-evidence and
explanation.", "A later reviewer failing to rediscover a candidate does not
withdraw it, and neither majority vo [...]
[[cases]]
id = "review_pair_limit_is_not_missing_evidence"
group = "review-pr independent review coordination"
-description = "Allows a third pair but stops at six assessments without
misclassifying exhaustion as unavailable evidence."
+description = "Limits each unchanged basis to six assessments in five stages
and distinguishes unresolved exhaustion from missing decisive evidence."
phase = "read-only review-convergence termination assessment"
ordinary_loop = false
profile = "review-pr"
semantic_policy_assertions = true
-prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. A: four complete independent
reviews on one unchanged basis form two pairs. The first pair disagrees and the
second pair still disagrees after substantive result review. No decisive fact
is unavailable, and further isolated reviews are available. Choose
A=continue-third-round or A=stop-not-conver [...]
+prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. A: four complete independent
assessments on one unchanged basis form three stages: an initial pair followed
by two single reports. After substantive result review, the overall ledger
still contains an outcome-sensitive candidate requiring adjudication. No
decisive fact is unavailable, and further [...]
decision = "proceed"
required_actions = []
allowed_actions = []
forbidden_actions = ["inspect_local", "edit_code", "edit_non_code",
"run_local_checks", "run_pre_handoff_review", "propose_commit_message",
"mutate_git", "mutate_remote"]
required_reasons = ["read_only_request"]
-required_summary_terms = ["A=continue-third-round",
"A_verdict=no-formal-result", "B=adjudicate-and-continue",
"C=review-incomplete", "D=report-missing-scope", "E=stop-not-converged",
"E_verdict=no-formal-result", "F=complete", "G=exclude-coordinator-and-checks"]
-forbidden_summary_terms = ["A=stop-not-converged",
"A_verdict=review-incomplete", "B=review-incomplete", "C=mergeable",
"D=claim-completed-dual-review", "E=continue-fourth-round",
"E_verdict=review-incomplete", "F=stop-not-converged",
"G=count-coordinator-and-checks"]
+required_summary_terms = ["A=obtain-one-fresh-assessment",
"A_verdict=no-formal-result", "B=adjudicate-and-continue",
"C=review-incomplete", "D=report-missing-scope", "E=stop-not-converged",
"E_verdict=no-formal-result", "F=complete", "G=exclude-coordinator-and-checks"]
+forbidden_summary_terms = ["A=stop-not-converged",
"A_verdict=review-incomplete", "B=review-incomplete", "C=mergeable",
"D=claim-completed-dual-review", "E=obtain-seventh-assessment",
"E_verdict=review-incomplete", "F=stop-not-converged",
"G=count-coordinator-and-checks"]
critical = true
policy_binding_profile = "review-pr"
-policy_assertions = [{source = "review-pr-coordination", statements = ["Permit
at most three pairs and six complete independent assessments for one unchanged
review basis.", "Coordinator result reviews, evidence checks and verification
reports do not count toward this limit.", "Do not select an arbitrary matching
pair across rounds or impose an unagreed round or time limit.", "Do not extend
the six-assessment limit without explicit user authorization.", "Otherwise
verify the differences, [...]
+policy_assertions = [{source = "review-pr-coordination", statements = ["Permit
at most six complete independent assessments in at most five stages for one
unchanged review basis: two in the first stage and one in each subsequent
stage.", "Coordinator result reviews, evidence checks and verification reports
do not count toward this limit.", "If confirmation remains unfinished, verify
available evidence and obtain only one fresh independent complete assessment at
a time while fewer than si [...]
[[cases]]
id = "review_confirmation_requires_fresh_adjacent_agreement"
group = "review-pr independent review coordination"
-description = "Requires a second fresh round and complete adjacent agreement
instead of adjudicator-only or nonadjacent matches."
+description = "Completes a verified cumulative assessment without report
equality or consecutive stability while preserving unresolved candidates."
phase = "read-only cross-round confirmation assessment"
ordinary_loop = false
profile = "review-pr"
semantic_policy_assertions = true
-prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. The following states are
independent reviews on unchanged established bases, with complete supported
evidence and historical dispositions except for the stated differences. A: the
first fresh pair and its substantive adjudication all agree on Mergeable.
Choose A=continue or A=complete. B: the sec [...]
+prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. The following states are
independent reviews on unchanged established bases. Complete current-basis
coverage, required verification and historical dispositions are established
except for the stated unresolved candidates. A: the initial pair and
substantive overall adjudication support Mergeable, [...]
decision = "proceed"
required_actions = []
allowed_actions = []
forbidden_actions = ["inspect_local", "edit_code", "edit_non_code",
"run_local_checks", "run_pre_handoff_review", "propose_commit_message",
"mutate_git", "mutate_remote"]
required_reasons = ["read_only_request"]
-required_summary_terms = ["A=continue", "B=complete", "C=continue",
"D=stop-not-converged", "E=continue", "F=complete"]
-forbidden_summary_terms = ["A=complete", "B=continue",
"C=complete-on-stable-adjudication", "D=accept-nonadjacent-match",
"E=accept-matching-counts", "F=continue-on-metadata-change"]
+required_summary_terms = ["A=complete", "B=complete", "C=continue",
"D=complete", "E=complete", "F=complete", "G=leave-result-fields-empty"]
+forbidden_summary_terms = ["A=continue", "B=require-identical-reports",
"C=complete-on-stable-adjudication", "D=require-another-report",
"E=require-stable-classification", "F=continue-on-metadata-change",
"G=publish-completed-result"]
critical = true
policy_binding_profile = "review-pr"
-policy_assertions = [{source = "review-pr-coordination", statements = ["Permit
at most three pairs and six complete independent assessments for one unchanged
review basis.", "If the third pair does not satisfy confirmation or a concrete
failure prevents continuing confirmation, report `Review Status: Not
Converged`, that reason, the confirmed facts and unresolved differences without
a formal `Review Result` line or a mergeability conclusion.", "If no previous
adjudication exists on this [...]
+policy_assertions = [{source = "review-pr-coordination", statements = ["Permit
at most six complete independent assessments in at most five stages for one
unchanged review basis: two in the first stage and one in each subsequent
stage.", "Confirm the overall assessment as soon as complete current-basis
coverage and required verification are established, every known candidate has
an evidence-based disposition, the supporting evidence and counter-evidence are
sufficient, and no unresolved [...]
[[cases]]
id = "review_confirmation_restarts_after_basis_change"
group = "review-pr independent review coordination"
-description = "Preserves history while requiring two consecutive current-basis
rounds after an input change."
+description = "Restarts with two current-basis reports after a real input
change while retaining immutable history and permitting immediate completion."
phase = "read-only review-basis reset assessment"
ordinary_loop = false
profile = "review-pr"
semantic_policy_assertions = true
-prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. A prior basis has two complete
confirming rounds. The effective candidate, an applicable requirement or rule
version, or an outcome-sensitive external fact then changes. A: choose
A=start-fresh-current-confirmation or A=reuse-old-confirmation. B: one fresh
pair and its full adjudication have comp [...]
+prompt = "Answer this self-contained read-only policy question using only the
supplied facts and bound rules; do not inspect files, run commands, edit
anything or produce a Formal Review report. A prior basis has a completed
verified overall assessment. The effective candidate, an applicable requirement
or rule version, or an outcome-sensitive external fact then changes. A: choose
A=start-fresh-current-confirmation or A=reuse-old-confirmation. B: an initial
pair has completed on the new [...]
decision = "proceed"
required_actions = []
allowed_actions = []
forbidden_actions = ["inspect_local", "edit_code", "edit_non_code",
"run_local_checks", "run_pre_handoff_review", "propose_commit_message",
"mutate_git", "mutate_remote"]
required_reasons = ["read_only_request"]
required_summary_terms = ["A=start-fresh-current-confirmation",
"B=continue-current-basis", "C=complete",
"D=preserve-history-and-reassess-current-evidence"]
-forbidden_summary_terms = ["A=reuse-old-confirmation",
"B=count-one-old-round", "C=discard-all-history", "D=delete-old-records",
"D=use-stale-anchors-as-current-proof"]
+forbidden_summary_terms = ["A=reuse-old-confirmation",
"B=count-one-old-round", "C=require-second-current-stage",
"C=discard-all-history", "D=delete-old-records",
"D=use-stale-anchors-as-current-proof"]
critical = true
policy_binding_profile = "review-pr"
-policy_assertions = [{source = "review-pr-coordination", statements = ["A
changed effective candidate, requirement, rule version or outcome-sensitive
external fact invalidates the affected prior assessments; never combine results
from different bases to pass this gate.", "Start fresh confirmation on the
changed basis while retaining the original task baseline, attributed scope,
historical candidates and original assessments.", "Only consecutive rounds on
the current basis count toward it [...]
+policy_assertions = [{source = "review-pr-coordination", statements = ["A
changed effective candidate, requirement, rule version or outcome-sensitive
external fact invalidates the affected prior assessments; never combine results
from different bases to pass this gate.", "Start fresh confirmation on the
changed basis while retaining the original task baseline, attributed scope,
historical candidates and original assessments.", "Obtain a new initial pair on
the changed basis; earlier basi [...]
[[cases]]
id = "review_confirmation_shares_read_only_and_coding_route"
diff --git a/.codex/skills/review-pr/SKILL.md b/.codex/skills/review-pr/SKILL.md
index efd493495df..b63796d27e3 100644
--- a/.codex/skills/review-pr/SKILL.md
+++ b/.codex/skills/review-pr/SKILL.md
@@ -55,8 +55,9 @@ coverage, or convergence. Never use a previous Formal Review
result as
evidence or as a conclusion to match. Treat previous public findings only as
hypotheses whose cited facts must be reverified.
-Two reviews with the same effective candidate, requirements, focus, and
-evidence must produce the same canonical assessment. Public-PR and
local-candidate reviews may resolve different candidates, but they must apply
the same code-correctness judgment and Formal Review result mapping. A changed
focus, requirement,
+Completed coordinated reviews with the same effective candidate, requirements,
focus, and evidence must produce the same canonical overall assessment.
+Individual independent assessments may discover different candidates; the
coordinator verifies and adjudicates their cumulative findings under
Independent Review Coordination.
+Public-PR and local-candidate reviews may resolve different candidates, but
they must apply the same code-correctness judgment and Formal Review result
mapping. A changed focus, requirement,
or external fact changes the review basis and may legitimately change the
assessment. Mergeability or CI evidence may add external-state findings or
gaps, but it must not change code-correctness findings derived from an
otherwise
@@ -315,7 +316,7 @@ For each blocking issue include:
Do not add patch-level changes after selecting Needs Discussion.
Do not include placeholder headings.
In `### Coverage`, report the candidate type, reviewed baseline or head,
authoritative requirements and files accounted for, accepted behavior and
project-commitment basis, behavior clusters, completed discovery lenses,
unresolved gaps, and CI scope.
-Also report the completed independent-review pairs, result-review outcome and
resolution of any differences without exposing private reviewer materials.
+Also report the completed independent assessments and stages, overall
adjudication and resolution of any differences without exposing private
reviewer materials.
For every public PR or PR-backed local candidate, also report the effective
candidate SHA, each style-verification scope and command, each exit code, every
covered applicable PR-impact file, and every `Not Applicable` check with its
basis.
For a standalone local candidate, identify the task baseline and attributed
local delta; for a PR-backed candidate, identify authorized local commits,
index changes, or working-tree changes separately from the public PR state.
In Code Correctness Review, state that the result is code-scope only and CI
was not reviewed while distinguishing the completed local style verification
from CI.
diff --git a/.codex/skills/review-pr/references/review-coordination.md
b/.codex/skills/review-pr/references/review-coordination.md
index 5f80066800c..9f810a28560 100644
--- a/.codex/skills/review-pr/references/review-coordination.md
+++ b/.codex/skills/review-pr/references/review-coordination.md
@@ -22,24 +22,24 @@ That outer loop alone owns code changes, their verification
and coding-task comp
## Independent Reviewer Roles
-For every top-level Formal Review and every discussion reply that makes or
changes an overall readiness conclusion, a coordinator must obtain two
independent complete assessments of the same effective candidate, original
baseline, authoritative scope, requirements and rule versions, and review focus.
+For every top-level Formal Review and every discussion reply that makes or
changes an overall readiness conclusion, a coordinator must first obtain two
mutually blind independent complete assessments of the same effective
candidate, original baseline, authoritative scope, requirements and rule
versions, and review focus.
A reply limited to one finding remains scoped to that finding and does not
require a full-candidate pair unless it makes an overall readiness conclusion.
Reviewers may discover different relevant raw evidence, but shared evidence
must refer to the same content version and all outcome-sensitive evidence must
be reconciled before completion.
Use isolated reviewer contexts within the active task; do not create another
user-owned task or send repository material to an external review service.
-Give every fresh pair the current requirements and their provenance, complete
candidate inventory and relevant raw evidence without the author's full session
history, earlier assessments, candidate lists or dispositions, a target
conclusion or an expected finding count.
+Give every independent reviewer the current requirements and their provenance,
complete candidate inventory and relevant raw evidence without the author's
full session history, earlier assessments, candidate lists or dispositions, a
target conclusion or an expected finding count.
Apply the [main Skill](../SKILL.md)'s evidence-reuse rules when sharing
current raw facts and verification evidence.
Never share reviewed-file statuses, candidate findings, proof conclusions or
convergence results between independent reviewers.
If isolated contexts are unavailable, report that the required independent
reviews could not be completed and do not claim a completed Formal Review.
Each independent reviewer runs the complete Review Workflow and Completion
Gate once, records a separate ledger and original assessment, and returns them
privately to the coordinator.
The independent-review role must not start another pair of reviews or publish
a final Formal Review result.
-Existing previous-miss, challenged-finding and high-risk triggers remain
applicable within each assessment; the required pair supplies their independent
review rather than adding another reviewer.
-Preserve every original assessment and ledger without rewriting them to make
the pair agree.
+Existing previous-miss, challenged-finding and high-risk triggers remain
applicable within each assessment; this coordination procedure supplies their
independent review rather than adding another reviewer.
+Preserve every original assessment and ledger without rewriting them to agree
with the overall adjudication.
## Review of the Reports
-The coordinator must substantively review both assessments and their
supporting evidence even when their verdicts and issue lists already match.
-Recheck each claim's full path, strongest counter-evidence, necessity, scope
and decision classification, and check the pair for duplicated issues, omitted
historical candidates and unsupported acceptance conclusions.
+The coordinator must substantively review every assessment and its supporting
evidence even when the verdicts and issue lists already match.
+Recheck each claim's full path, strongest counter-evidence, necessity, scope
and decision classification, and check the cumulative reports for duplicated
issues, omitted historical candidates and unsupported acceptance conclusions.
Normalize issues by root cause, trigger and independent fix boundary; compare
the complete issue sets, evidence gaps, discussion conditions, verdicts and
feedback modes rather than counts, order or wording.
Record an evidence-based disposition for every candidate raised in any
assessment, including retained, withdrawn or merged candidates, with its
supporting evidence, strongest counter-evidence and explanation.
A later reviewer failing to rediscover a candidate does not withdraw it, and
neither majority voting nor repeated matching subsets can discard a historical
candidate.
@@ -47,28 +47,28 @@ The coordinator's result review is focused verification of
the reports and their
## Cross-Round Confirmation
-Permit at most three pairs and six complete independent assessments for one
unchanged review basis.
+Permit at most six complete independent assessments in at most five stages for
one unchanged review basis: two in the first stage and one in each subsequent
stage.
Coordinator result reviews, evidence checks and verification reports do not
count toward this limit.
Before changing an overall conclusion, compare the prior and current effective
candidates, requirement sources and versions, applicable exceptions, review
focus, evidence identities and finding sets; candidate-type or Git revision
labels and a different previous verdict do not establish a changed basis.
A changed effective candidate, requirement, rule version or outcome-sensitive
external fact invalidates the affected prior assessments; never combine results
from different bases to pass this gate.
Start fresh confirmation on the changed basis while retaining the original
task baseline, attributed scope, historical candidates and original assessments.
-Only consecutive rounds on the current basis count toward its confirmation;
earlier basis phases remain historical records.
-
-1. Obtain a fresh independent pair under Independent Reviewer Roles and
complete Review of the Reports.
-2. Append the original reports and the coordinator's complete adjudication for
this round, including every historical candidate's current disposition.
-3. If no previous adjudication exists on this basis, retain this first
adjudication as the comparison baseline and start a fresh round; the first
round cannot complete confirmation.
-4. Compare the previous and current complete adjudications, including every
canonical issue's identity, status and classification, evidence gaps,
discussion conditions, result and feedback mode.
- Compare unchanged decisions semantically; reviewer identities, member
mappings, file hashes, evidence wording and report ordering do not establish a
changed conclusion.
-5. Confirm only when the fresh pair's normalized complete assessments agree
with each other, the previous adjudication and the current adjudication, the
supporting evidence and counter-evidence are sufficient, and no unresolved
issue can change the conclusion.
+Obtain a new initial pair on the changed basis; earlier basis phases remain
historical records and do not count toward its six-assessment limit or current
coverage.
+
+1. Obtain the initial independent pair under Independent Reviewer Roles and
complete Review of the Reports.
+2. Record the coordinator's verified overall ledger in the session's
cumulative `rounds[].findings`, preserving the original reports and every
historical candidate's current disposition.
+3. Confirm the overall assessment as soon as complete current-basis coverage
and required verification are established, every known candidate has an
evidence-based disposition, the supporting evidence and counter-evidence are
sufficient, and no unresolved issue can change the conclusion.
+ The first stage may complete confirmation; equality between independent
reports or stability between consecutive adjudications is not required.
An evidenced need for a maintainer decision may be a confirmed `Needs
Discussion` condition; it does not establish a code defect.
-6. Otherwise verify the differences, record their evidence-based dispositions
without changing the original assessments, retain the updated current
adjudication as the next comparison baseline and start a fresh round only while
fewer than three pairs have completed on this basis.
+4. If confirmation remains unfinished, verify available evidence and obtain
only one fresh independent complete assessment at a time while fewer than six
assessments have completed on this basis.
+5. Substantively review the new report, compare it with the retained overall
ledger, adjudicate every new or disputed candidate and append the updated
cumulative adjudication before checking completion again.
+ Compare decisions semantically; reviewer identities, member mappings, file
hashes, evidence wording and report ordering do not establish a changed
conclusion.
-Any substantive issue, classification, disposition, evidence-gap or readiness
change requires a subsequent fresh pair within this limit; if none remains, do
not confirm the changed conclusion.
-The coordinator must retain and compare the prior adjudication even though the
fresh reviewers must not receive it.
-Do not select an arbitrary matching pair across rounds or impose an unagreed
round or time limit.
+A newly proved finding or an evidence-based change in status, classification,
disposition, gap or readiness does not by itself require another assessment
once the completion conditions hold.
+The coordinator must retain and compare prior adjudications even though fresh
reviewers must not receive them.
+Do not select an arbitrary matching subset of reports or impose an unagreed
stage or time limit.
Do not extend the six-assessment limit without explicit user authorization.
-If the third pair does not satisfy confirmation or a concrete failure prevents
continuing confirmation, report `Review Status: Not Converged`, that reason,
the confirmed facts and unresolved differences without a formal `Review Result`
line or a mergeability conclusion.
+If confirmation remains unfinished after six assessments or a concrete failure
prevents continuing confirmation, report `Review Status: Not Converged`, that
reason, the confirmed facts and unresolved differences without a formal `Review
Result` line or a mergeability conclusion.
Disagreement, exhausted review attempts or an unavailable isolation mechanism
do not satisfy the Review Incomplete Proof Gate.
When authoritative scope cannot be established, preserve the Review Incomplete
Proof Gate's missing-scope evidence route and state explicitly that complete
independent code reviews could not start.
@@ -81,22 +81,23 @@ Each reviewer writes one private original result JSON
containing `reviewer`, the
Use a distinct isolated reviewer identity for every assessment, compute hashes
from the frozen file bytes, and keep the original result and ledger unchanged
through session validation.
Use `Mergeable`, `Not Mergeable` or `Review Incomplete` for `result`, and an
empty string, `Change Request` or `Needs Discussion` for `feedback_mode` as
required by the Formal Decision Contract.
-The coordinator's private session JSON keeps `reviews` in consecutive pairs
and one `rounds` entry per pair, using the following record shape:
+The coordinator's private session JSON keeps `reviews` in assessment order and
one cumulative `rounds` entry per stage, with two reports in the first stage of
each basis and one in every subsequent stage, using the following record shape:
```json
{"reviews": [{"path": "<original-result JSON>", "sha256": "<original-result
SHA256>"}], "rounds": [{"result": "<this round's adjudicated result>",
"feedback_mode": "<this round's adjudicated feedback mode>", "report_reviews":
[{"reviewer": "<identity>", "reason": "<result and evidence recheck>",
"counter_evidence": "<strongest contrary evidence checked>", "evidence":
[{"reviewer": "<identity>", "anchor": {}}]}], "findings": [{"id": "<stable
canonical issue ID>", "members": [{"reviewer": [...]
```
-Include a `report_reviews` entry for both reviewers in every pair, even for
matching empty issue lists, and substantiate the result classification and
acceptance premises as well as individual findings.
+Include a `report_reviews` entry for every new report in its stage, even for
an empty issue list, and substantiate the result classification and acceptance
premises as well as individual findings.
Replace every example `anchor` with a complete captured real-source anchor
from that reviewer's ledger, including its object and range hashes; an empty
object or a free-form citation does not establish evidence.
-Each round's `findings` must cover every original candidate raised up to that
round, map each reviewer and finding exactly once, and retain earlier canonical
IDs with their updated evidence-based disposition.
+Each stage's `findings` must cover every original candidate raised up to that
stage, map each reviewer and finding exactly once, and retain earlier canonical
IDs with their updated evidence-based disposition.
Use the existing finding statuses; a duplicate may map to the same canonical
issue, while a merged earlier canonical ID remains recorded as withdrawn with
the merge target explained in `reason`.
Use `classification` to describe the issue type, discussion condition or
evidence gap in stable text; it is not a new status enumeration.
An issue discovered during result review may have empty `members` but still
requires real evidence, a disposition and counter-evidence; an unresolved
candidate never permits a completed result.
+An intermediate stage containing any finding with `candidate` status must
leave both `result` and `feedback_mode` empty.
After a basis change, retain the earlier rounds and original reports for
historical completeness and verify the current dispositions from evidence valid
on the current basis.
Do not treat an earlier basis's frozen evidence as current or require it to
pass current-candidate freshness checks.
-The final pair's original result, feedback mode, normalized confirmed issues
and evidence gaps must agree with each other and with both final consecutive
adjudications and the proposed session result.
-Complete historical dispositions, including their classifications, must also
be stable between those adjudications.
+The final cumulative adjudication must satisfy the completion conditions,
contain no unresolved candidate and match the proposed session result and
feedback mode.
+Individual reports may differ from that final adjudication only with recorded
evidence-based dispositions that explain each difference.
Before publishing a completed Formal Review result or an overall readiness
conclusion in a discussion reply, run `scripts/review_ledger.py
validate-session --session <JSON>` after the coordinator's substantive result
review.
The command is read-only and validates records, references and evidence
freshness; it neither launches reviewers nor proves their isolation or semantic
correctness.
diff --git a/.codex/skills/review-pr/scripts/review_ledger.py
b/.codex/skills/review-pr/scripts/review_ledger.py
index 941933711de..a5de373d023 100755
--- a/.codex/skills/review-pr/scripts/review_ledger.py
+++ b/.codex/skills/review-pr/scripts/review_ledger.py
@@ -782,7 +782,8 @@ def review_evidence_versions(ledger: dict[str, Any]) ->
dict[tuple[str, str, str
return result
-def result_review_errors(proof: Any, reviews: dict[str, dict[str, Any]],
own_reviewer: str = "", current_basis: bool = True) -> list[str]:
+def result_review_errors(proof: Any, reviews: dict[str, dict[str, Any]],
basis_ledger: dict[str, Any],
+ source_ledgers: dict[str, dict[str, Any]],
own_reviewer: str = "", current_basis: bool = True) -> list[str]:
if not isinstance(proof, dict) or any(not has_text(proof.get(key)) for key
in ("reason", "counter_evidence")):
raise RuntimeError("Result review requires an evidence assessment and
strongest counter-evidence")
if not isinstance(proof.get("evidence"), list) or not proof["evidence"]:
@@ -800,17 +801,25 @@ def result_review_errors(proof: Any, reviews: dict[str,
dict[str, Any]], own_rev
ledger = reviews[reviewer]["ledger"]
if not current_basis and anchor not in all_anchors(ledger):
errors.append("Historical result-review evidence is not recorded
in its original ledger")
- elif current_basis and capture_anchor(anchor, ledger) != anchor:
- errors.append("Result-review evidence is stale")
+ elif current_basis:
+ evidence_ledger = basis_ledger
+ current_anchor = anchor
+ if "source" == anchor["side"]:
+ if not any(each["origin"] == anchor["origin"] and each["path"]
== anchor["path"] for each in ledger["basis"]["sources"]):
+ raise RuntimeError("Source anchor does not match a
declared material and origin")
+ evidence_ledger = source_ledgers.get(anchor["origin"], ledger)
+ material_path = next(each["path"] for each in
evidence_ledger["basis"]["sources"] if each["origin"] == anchor["origin"])
+ current_anchor = {**anchor, "path": material_path}
+ if capture_anchor(current_anchor, evidence_ledger) !=
current_anchor:
+ errors.append("Result-review evidence is stale")
return errors
def validate_session(session: Any) -> list[str]:
- if not isinstance(session, dict) or not isinstance(session.get("reviews"),
list) or len(session["reviews"]) < 2 or len(session["reviews"]) % 2:
- raise RuntimeError("A review session requires complete pairs of
original results")
- if not isinstance(session.get("rounds"), list) or len(session["rounds"]) *
2 != len(session["reviews"]):
- raise RuntimeError("Every review pair requires its own substantive
result review")
- validate_review_decision(session)
+ if not isinstance(session, dict) or not isinstance(session.get("reviews"),
list) or len(session["reviews"]) < 2:
+ raise RuntimeError("A review session requires at least two original
results")
+ if not isinstance(session.get("rounds"), list) or not session["rounds"]:
+ raise RuntimeError("A review session requires substantive
result-review stages")
records = [read_review_record(each) for each in session["reviews"]]
for key in ("reviewer", "path", "ledger_file"):
if len(records) != len({each[key] for each in records}):
@@ -819,49 +828,66 @@ def validate_session(session: Any) -> list[str]:
if len({each["ledger"]["basis"]["baseline"]["tree"] for each in records})
!= 1:
errors.append("The original task baseline changed across review
rounds")
current_start = 0
+ current_review_start = 0
+ review_offset = 0
+ basis_assessments = 0
previous_identity = None
source_versions = {}
- for index in range(len(session["rounds"])):
- pair = records[index * 2:index * 2 + 2]
- identity = review_input_identity(pair[0]["ledger"])
- if identity != review_input_identity(pair[1]["ledger"]):
- errors.append("Review candidates, original baselines, requirements
or focus differ within a pair")
- pair_versions = {}
- for record in pair:
+ stage_records = []
+ for index, review_round in enumerate(session["rounds"]):
+ if not isinstance(review_round, dict) or not
isinstance(review_round.get("report_reviews"), list) or not
isinstance(review_round.get("findings"), list):
+ raise RuntimeError("Every stage requires report reviews and
explicit finding dispositions")
+ report_count = len(review_round["report_reviews"])
+ if report_count not in {1, 2}:
+ raise RuntimeError("Every review stage requires one or two
original reports")
+ stage = records[review_offset:review_offset + report_count]
+ if len(stage) != report_count:
+ raise RuntimeError("Review stages must account for every original
result exactly once")
+ stage_records.append(stage)
+ identity = review_input_identity(stage[0]["ledger"])
+ if any(identity != review_input_identity(each["ledger"]) for each in
stage[1:]):
+ errors.append("Review candidates, original baselines, requirements
or focus differ within a stage")
+ stage_versions = {}
+ for record in stage:
versions = review_evidence_versions(record["ledger"])
- if any(key in pair_versions and pair_versions[key] != value for
key, value in versions.items()):
+ if any(key in stage_versions and stage_versions[key] != value for
key, value in versions.items()):
errors.append("Independent reviews disagree on a shared source
version")
- pair_versions.update(versions)
- if identity != previous_identity or any(key in source_versions and
source_versions[key] != value for key, value in pair_versions.items()):
+ stage_versions.update(versions)
+ if identity != previous_identity or any(key in source_versions and
source_versions[key] != value for key, value in stage_versions.items()):
+ if report_count != 2:
+ errors.append("Each review basis requires an initial pair of
independent original assessments")
current_start = index
+ current_review_start = review_offset
+ basis_assessments = 0
source_versions = {}
- source_versions.update(pair_versions)
+ source_versions.update(stage_versions)
+ basis_assessments += report_count
+ if basis_assessments > 6:
+ errors.append("The current review basis exceeds six independent
original assessments")
previous_identity = identity
- current_pairs = len(session["rounds"]) - current_start
- if current_pairs > 3:
- errors.append("The current review basis exceeds six independent
original assessments")
- if current_pairs < 2:
- errors.append("The current review basis requires at least two
consecutive independent pairs")
+ review_offset += report_count
+ if review_offset != len(records):
+ raise RuntimeError("Review stages must account for every original
result exactly once")
for index, record in enumerate(records):
ledger = record["ledger"]
- errors.extend(_validate_ledger(ledger, incomplete_result="Review
Incomplete" == record["result"], current_basis=index >= current_start * 2))
+ errors.extend(_validate_ledger(ledger, incomplete_result="Review
Incomplete" == record["result"], current_basis=index >= current_review_start))
confirmed = any(each["status"] == "confirmed" for each in
ledger["findings"])
if "Review Incomplete" != record["result"] and confirmed != ("Not
Mergeable" == record["result"]):
errors.append("An original decision contradicts its confirmed
finding set")
+ basis_ledger = records[current_review_start]["ledger"]
+ source_ledgers = {source["origin"]: record["ledger"] for record in
records[current_review_start:]
+ for source in record["ledger"]["basis"]["sources"]}
known_reviews = {}
original_findings = set()
prior_ids = set()
final_groups = []
- final_members = {}
- assessments = []
for index, review_round in enumerate(session["rounds"]):
- if not isinstance(review_round, dict) or not
isinstance(review_round.get("report_reviews"), list) or not
isinstance(review_round.get("findings"), list):
- raise RuntimeError("Every pair requires report reviews and
explicit finding dispositions")
- validate_review_decision(review_round)
current_basis = index >= current_start
- pair = records[index * 2:index * 2 + 2]
- known_reviews.update((each["reviewer"], each) for each in pair)
- for record in pair:
+ stage = stage_records[index]
+ if index > current_start and len(stage) != 1:
+ errors.append("Later stages on an unchanged review basis require
one independent original assessment")
+ known_reviews.update((each["reviewer"], each) for each in stage)
+ for record in stage:
original_findings.update((record["reviewer"], each["id"]) for each
in record["ledger"]["findings"])
audited_reviewers = []
for report_review in review_round["report_reviews"]:
@@ -869,9 +895,10 @@ def validate_session(session: Any) -> list[str]:
raise RuntimeError("Every report review requires its original
reviewer identity")
reviewer = report_review["reviewer"]
audited_reviewers.append(reviewer)
- errors.extend(result_review_errors(report_review, known_reviews,
own_reviewer=reviewer, current_basis=current_basis))
- if Counter(audited_reviewers) != Counter(each["reviewer"] for each in
pair):
- errors.append("Both original reports require substantive result
review, even when they agree")
+ errors.extend(result_review_errors(report_review, known_reviews,
basis_ledger, source_ledgers,
+ own_reviewer=reviewer,
current_basis=current_basis))
+ if Counter(audited_reviewers) != Counter(each["reviewer"] for each in
stage):
+ errors.append("Every original report requires substantive result
review in its stage")
groups, members = {}, {}
for finding in review_round["findings"]:
if (not isinstance(finding, dict) or not
has_text(finding.get("id")) or not has_text(finding.get("classification"))
@@ -882,7 +909,7 @@ def validate_session(session: Any) -> list[str]:
if finding["id"] in groups:
errors.append("Canonical finding identities are duplicated")
groups[finding["id"]] = finding
- errors.extend(result_review_errors(finding, known_reviews,
current_basis=current_basis))
+ errors.extend(result_review_errors(finding, known_reviews,
basis_ledger, source_ledgers, current_basis=current_basis))
for member in finding["members"]:
if not isinstance(member, dict) or any(not
has_text(member.get(key)) for key in ("reviewer", "finding")):
raise RuntimeError("Finding mappings require an original
reviewer and finding identity")
@@ -895,30 +922,23 @@ def validate_session(session: Any) -> list[str]:
if prior_ids - groups.keys():
errors.append("A previous result-review finding disappeared
without a disposition")
prior_ids = set(groups)
- final_groups, final_members = list(groups.values()), members
- confirmed = {each["id"] for each in final_groups if each["status"] ==
"confirmed"}
- gaps = {each["id"] for each in final_groups if each["status"] ==
"review-incomplete-gap"}
- expected_result = "Review Incomplete" if gaps else "Not Mergeable" if
confirmed else "Mergeable"
- if review_round["result"] != expected_result:
- errors.append("A round decision contradicts its complete
historical dispositions")
- assessments.append((review_round["result"],
review_round["feedback_mode"],
- frozenset((each["id"], each["status"],
each["classification"]) for each in final_groups)))
+ final_groups = list(groups.values())
+ if any(each["status"] == "candidate" for each in final_groups):
+ if (review_round.get("result"), review_round.get("feedback_mode"))
!= ("", ""):
+ errors.append("Unresolved result-review candidates require an
empty result and feedback mode")
+ else:
+ validate_review_decision(review_round)
+ confirmed = any(each["status"] == "confirmed" for each in
final_groups)
+ gaps = any(each["status"] == "review-incomplete-gap" for each in
final_groups)
+ expected_result = "Review Incomplete" if gaps else "Not Mergeable"
if confirmed else "Mergeable"
+ if review_round["result"] != expected_result:
+ errors.append("A round decision contradicts its complete
historical dispositions")
if any(each["status"] == "candidate" for each in final_groups):
errors.append("Result-review candidates remain unresolved")
- if current_pairs >= 2 and assessments[-2] != assessments[-1]:
- action = "obtain a fresh independent pair" if current_pairs < 3 else
"stop and report Review Status: Not Converged"
- errors.append("The latest complete adjudication differs from the
previous round; " + action)
- if (session["result"], session["feedback_mode"]) != assessments[-1][:2]:
- errors.append("The final decision contradicts the latest complete
adjudication")
- expected = (session["result"], session["feedback_mode"], confirmed, gaps)
- expected_dispositions = {(each["id"], each["status"]) for each in
final_groups}
- for record in records[-2:]:
- dispositions = {(final_members[(record["reviewer"], each["id"])],
each["status"]) for each in record["ledger"]["findings"]
- if (record["reviewer"], each["id"]) in final_members}
- finding_sets = [{finding_id for finding_id, finding_status in
dispositions if finding_status == status}
- for status in ("confirmed", "review-incomplete-gap")]
- if (record["result"], record["feedback_mode"], *finding_sets) !=
expected or not dispositions.issubset(expected_dispositions):
- errors.append("The latest pair does not agree with the result
review and all historical findings")
+ else:
+ validate_review_decision(session)
+ if (session["result"], session["feedback_mode"]) !=
(session["rounds"][-1]["result"], session["rounds"][-1]["feedback_mode"]):
+ errors.append("The final decision contradicts the latest complete
adjudication")
return errors
@@ -928,7 +948,7 @@ def cmd_validate_session(args: argparse.Namespace) -> int:
for error in errors:
print(f"ERROR: {error}")
return 1
- print("OK: review pair and substantive result-review records are complete;
semantic judgment remains the reviewer's responsibility")
+ print("OK: review stages and substantive result-review records are
complete; semantic judgment remains the reviewer's responsibility")
return 0
diff --git a/.codex/skills/review-pr/tests/test_review_ledger.py
b/.codex/skills/review-pr/tests/test_review_ledger.py
index 2868e593c89..75e0d6e8a76 100644
--- a/.codex/skills/review-pr/tests/test_review_ledger.py
+++ b/.codex/skills/review-pr/tests/test_review_ledger.py
@@ -643,7 +643,7 @@ class ReviewLedgerTest(unittest.TestCase):
report_reviews[0]["evidence"][0]["anchor"]["end_line"] = 99
else:
report_reviews[0][missing] = [] if "evidence" == missing
else " "
- if missing in {"reason", "counter_evidence", "evidence",
"valid_range"}:
+ if missing in {"report", "reason", "counter_evidence",
"evidence", "valid_range"}:
self.assert_session_cli_rejected(actual)
else:
self.assertTrue(ledger.validate_session(actual))
@@ -702,15 +702,13 @@ class ReviewLedgerTest(unittest.TestCase):
actual["rounds"][-1]["findings"][0]["status"] = "candidate"
self.assertTrue(ledger.validate_session(actual))
- def
test_session_rejects_same_count_with_different_findings_or_feedback(self):
+ def test_session_accepts_different_independent_findings_and_feedback(self):
actual = self.create_session([[("F1", "confirmed")], [("F2",
"confirmed")]])
- self.assertTrue(ledger.validate_session(actual))
- for review_round in actual["rounds"]:
-
review_round["findings"][0]["members"].extend(review_round["findings"].pop()["members"])
+ self.assertEqual([], ledger.validate_session(actual))
report =
json.loads(Path(actual["reviews"][-1]["path"]).read_text(encoding="utf-8"))
report["feedback_mode"] = "Needs Discussion"
self.save_session_report(actual, len(actual["reviews"]) - 1, report)
- self.assertTrue(ledger.validate_session(actual))
+ self.assertEqual([], ledger.validate_session(actual))
def test_session_preserves_finding_discovered_during_result_review(self):
original = self.create_session([[], [], [("F1", "confirmed")], [("F1",
"confirmed")]])
@@ -723,59 +721,63 @@ class ReviewLedgerTest(unittest.TestCase):
})
original["rounds"][0].update({"result": "Not Mergeable",
"feedback_mode": "Change Request"})
first_pair = json.loads(json.dumps(original))
- first_pair.update({"reviews": first_pair["reviews"][:2], "rounds":
first_pair["rounds"][:1], "result": "Mergeable", "feedback_mode": ""})
- self.assertTrue(ledger.validate_session(first_pair))
+ first_pair.update({"reviews": first_pair["reviews"][:2], "rounds":
first_pair["rounds"][:1],
+ "result": "Not Mergeable", "feedback_mode": "Change
Request"})
+ self.assertEqual([], ledger.validate_session(first_pair))
missing_identity = json.loads(json.dumps(original))
missing_identity["rounds"][1]["findings"][0]["id"] = "replacement-F1"
self.assertTrue(ledger.validate_session(missing_identity))
self.assertEqual([], ledger.validate_session(original))
- def test_session_requires_historical_findings_in_final_pair(self):
+ def
test_session_retains_historical_findings_omitted_by_later_reports(self):
original = self.create_session([[("F1", "confirmed")], [("F1",
"confirmed"), ("F2", "confirmed")],
[("F1", "confirmed")], [("F1",
"confirmed")],
[("F1", "confirmed")], [("F1",
"confirmed")]])
- self.assertTrue(ledger.validate_session(original))
+ self.assertEqual([], ledger.validate_session(original))
actual = json.loads(json.dumps(original))
actual["rounds"][-1]["findings"].pop()
self.assertTrue(ledger.validate_session(actual))
actual = json.loads(json.dumps(original))
- for review_round in actual["rounds"][-2:]:
- review_round["findings"][-1]["status"] = "withdrawn"
+ actual["rounds"][-1]["findings"][-1].update({
+ "status": "withdrawn", "reason": "Current source evidence
disproves the historical triggering path.",
+ "evidence": actual["rounds"][-1]["report_reviews"][0]["evidence"],
+ })
self.assertEqual([], ledger.validate_session(actual))
- def test_session_allows_more_than_two_complete_pairs(self):
- actual = self.create_session([[("F1", "confirmed")], [], [("F1",
"confirmed")], [("F1", "confirmed")],
- [("F1", "confirmed")], [("F1",
"confirmed")]])
- actual["rounds"][-1]["findings"][0].update({"reason": "The fresh
source evidence confirms the same triggering path.",
- "evidence":
actual["rounds"][-1]["report_reviews"][0]["evidence"]})
- self.assertEqual([], ledger.validate_session(actual))
+ def test_session_accepts_two_through_six_original_assessments(self):
+ for count in range(2, 7):
+ with self.subTest(count=count):
+ actual = self.create_session([[("F1", "confirmed")]] + [[]] *
(count - 1))
+ actual["rounds"][-1]["findings"][0].update({"reason": "The
fresh source evidence confirms the same triggering path.",
+ "evidence":
actual["rounds"][-1]["report_reviews"][0]["evidence"]})
+ self.assertEqual([], ledger.validate_session(actual))
- def test_session_rejects_more_than_three_complete_pairs(self):
- actual = self.create_session([[]] * 8)
+ def test_session_rejects_seventh_original_assessment(self):
+ actual = self.create_session([[]] * 7)
self.assertIn("The current review basis exceeds six independent
original assessments", ledger.validate_session(actual))
- def test_session_stops_after_third_unconfirmed_pair(self):
- actual = self.create_session([[("F1", "confirmed")]] * 6)
- actual["rounds"][-1]["findings"][0]["classification"] = "performance"
- self.assertIn("The latest complete adjudication differs from the
previous round; stop and report Review Status: Not Converged",
ledger.validate_session(actual))
-
- def test_session_requires_confirmation_after_first_round(self):
- actual = self.create_session([[], []])
- actual.update({"reviews": actual["reviews"][:2], "rounds":
actual["rounds"][:1]})
- self.assertTrue(ledger.validate_session(actual))
+ def test_session_accepts_new_confirmed_finding_in_sixth_assessment(self):
+ actual = self.create_session([[], [], [], [], [], [("F1",
"confirmed")]])
+ self.assertEqual([], ledger.validate_session(actual))
- def test_session_requires_stable_complete_dispositions(self):
+ def test_session_accepts_evidenced_final_disposition_changes(self):
original = self.create_session([[("F1", "withdrawn")]] * 6)
for field, value in (("classification", "performance"), ("status",
"non-blocking")):
with self.subTest(field=field):
actual = json.loads(json.dumps(original))
- actual["rounds"][-1]["findings"][0][field] = value
- self.assertTrue(ledger.validate_session(actual))
+ finding = actual["rounds"][-1]["findings"][0]
+ finding.update({field: value, "reason": "The current owning
contract supports this revised disposition.",
+ "evidence":
actual["rounds"][-1]["report_reviews"][0]["evidence"]})
+ self.assertEqual([], ledger.validate_session(actual))
- def test_session_requires_stable_feedback_mode(self):
- actual = self.create_session([[("F1", "confirmed")], [("F1",
"confirmed")]])
- actual["rounds"][-2]["feedback_mode"] = "Needs Discussion"
- self.assertTrue(ledger.validate_session(actual))
+ def test_session_accepts_evidenced_final_feedback_change(self):
+ actual = self.create_session([[("F1", "confirmed")], [("F1",
"confirmed")], []])
+ actual["rounds"][0]["feedback_mode"] = "Needs Discussion"
+ actual["rounds"][-1]["findings"][0].update({
+ "reason": "Current source evidence establishes a required
correctness fix.",
+ "evidence": actual["rounds"][-1]["report_reviews"][0]["evidence"],
+ })
+ self.assertEqual([], ledger.validate_session(actual))
def test_session_requires_final_closed_dispositions(self):
for status in ("withdrawn", "non-blocking", "out-of-scope"):
@@ -784,25 +786,59 @@ class ReviewLedgerTest(unittest.TestCase):
for reference in actual["reviews"]:
report =
json.loads(Path(reference["path"]).read_text(encoding="utf-8"))
self.assertEqual([],
ledger.validate_ledger(ledger.read_ledger(report["ledger"])))
- errors = ledger.validate_session(actual)
- if "withdrawn" == status:
- self.assertEqual([], errors)
- else:
- self.assertIn("The latest pair does not agree with the
result review and all historical findings", errors)
+ actual["rounds"][-1]["findings"][0].update({
+ "status": status, "reason": "The current owner contract
supports the coordinator's final disposition.",
+ "evidence":
actual["rounds"][-1]["report_reviews"][0]["evidence"],
+ })
+ self.assertEqual([], ledger.validate_session(actual))
- def test_session_rejects_repeated_unconfirmed_adjudication(self):
- actual = self.create_session([[("F1", "confirmed")], []])
+ def test_session_rejects_unresolved_final_adjudication(self):
+ actual = self.create_session([[("F1", "confirmed")]] * 6)
+ actual["rounds"][-1]["findings"][0]["status"] = "candidate"
+ actual["rounds"][-1].update({"result": "", "feedback_mode": ""})
self.assertTrue(ledger.validate_session(actual))
- def test_session_requires_complete_review_pairs(self):
+ def test_session_requires_two_initial_original_assessments(self):
original = self.create_session([[], []])
- for count in (0, 1, 3):
+ for count in (0, 1):
with self.subTest(count=count):
actual = json.loads(json.dumps(original))
actual["reviews"] = (actual["reviews"] * 2)[:count]
with self.assertRaises(RuntimeError):
ledger.validate_session(actual)
+ def test_session_requires_complete_initial_and_singleton_stages(self):
+ original = self.create_session([[], [], [], []])
+ for invalid in ("initial_singleton", "later_pair", "missing_stage",
"duplicate_report", "wrong_stage"):
+ with self.subTest(invalid=invalid):
+ actual = json.loads(json.dumps(original))
+ if "initial_singleton" == invalid:
+ first_stage = actual["rounds"][0]
+ second_stage = json.loads(json.dumps(first_stage))
+ second_stage["report_reviews"] =
[first_stage["report_reviews"].pop()]
+ actual["rounds"].insert(1, second_stage)
+ elif "later_pair" == invalid:
+
actual["rounds"][1]["report_reviews"].extend(actual["rounds"].pop()["report_reviews"])
+ elif "missing_stage" == invalid:
+ actual["rounds"].pop()
+ elif "duplicate_report" == invalid:
+ actual["rounds"][1]["report_reviews"] =
actual["rounds"][0]["report_reviews"][:1]
+ else:
+ actual["rounds"][1]["report_reviews"],
actual["rounds"][2]["report_reviews"] = \
+ actual["rounds"][2]["report_reviews"],
actual["rounds"][1]["report_reviews"]
+ self.assert_session_cli_rejected(actual)
+
+ def
test_session_keeps_intermediate_candidates_without_a_formal_verdict(self):
+ original = self.create_session([[("F1", "confirmed")], [], []])
+ original["rounds"][0]["findings"][0]["status"] = "candidate"
+ original["rounds"][0].update({"result": "", "feedback_mode": ""})
+ self.assertEqual([], ledger.validate_session(original))
+ for result, feedback in (("Not Mergeable", "Change Request"), ("",
"Change Request")):
+ with self.subTest(result=result, feedback=feedback):
+ actual = json.loads(json.dumps(original))
+ actual["rounds"][0].update({"result": result, "feedback_mode":
feedback})
+ self.assert_session_cli_rejected(actual)
+
def test_session_rejects_reused_or_modified_original_records(self):
original = self.create_session([[], []])
original_report =
Path(original["reviews"][1]["path"]).read_text(encoding="utf-8")
@@ -878,7 +914,7 @@ class ReviewLedgerTest(unittest.TestCase):
def test_session_reconfirms_changed_basis_and_retains_history(self):
for changed in ("candidate", "evidence"):
with self.subTest(changed=changed):
- historical = self.create_session([[("F1", "confirmed")],
[("F1", "confirmed")]])
+ historical = self.create_session([[("F1", "confirmed")],
[("F1", "confirmed")], []])
if "candidate" == changed:
(self.repo /
self.source_path).write_text("first\nfixed_first\nthird\nfourth\nfifth\nnew_second\nlast\n",
encoding="utf-8")
else:
@@ -891,13 +927,142 @@ class ReviewLedgerTest(unittest.TestCase):
"evidence":
review_round["report_reviews"][0]["evidence"]})
actual = {**current, "reviews": historical["reviews"] +
current["reviews"],
"rounds": historical["rounds"] + current["rounds"]}
- pending = {**actual, "reviews": actual["reviews"][:-2],
"rounds": actual["rounds"][:-1]}
- self.assertTrue(ledger.validate_session(pending))
+ pending = json.loads(json.dumps(actual))
+ pending["reviews"].pop()
+ pending["rounds"][-1]["report_reviews"].pop()
+ self.assertIn("Each review basis requires an initial pair of
independent original assessments", ledger.validate_session(pending))
self.assertEqual([], ledger.validate_session(actual))
+ if "candidate" == changed:
+ stale = json.loads(json.dumps(actual))
+ stale["rounds"][-1]["findings"][0]["evidence"] =
historical["rounds"][-1]["findings"][0]["evidence"]
+ self.assertTrue(ledger.validate_session(stale))
original_report = Path(historical["reviews"][0]["path"])
original_report.write_bytes(original_report.read_bytes() +
b"\n")
self.assert_session_cli_rejected(actual)
+ def test_session_rejects_exhausted_historical_basis(self):
+ historical = self.create_session([[]] * 7)
+ (self.repo /
self.source_path).write_text("first\nnext_first\nthird\nfourth\nfifth\nnew_second\nlast\n",
encoding="utf-8")
+ current = self.create_session([[], []],
review_start=len(historical["reviews"]))
+ actual = {**current, "reviews": historical["reviews"] +
current["reviews"],
+ "rounds": historical["rounds"] + current["rounds"]}
+ self.assertIn("The current review basis exceeds six independent
original assessments", ledger.validate_session(actual))
+
+ def test_session_rechecks_historical_committed_candidate_proof(self):
+ self.git("add", self.source_path)
+ self.git("commit", "-qm", "Synthetic reviewed candidate")
+ historical = self.create_session([[("F1", "confirmed")], []],
committed=True)
+ (self.repo /
self.source_path).write_text("first\nfixed_first\nthird\nfourth\nfifth\nnew_second\nlast\n",
encoding="utf-8")
+ self.git("add", self.source_path)
+ self.git("commit", "-qm", "Synthetic changed candidate")
+ current = self.create_session([[], []], review_start=2, committed=True)
+ current["rounds"][0].update({"findings":
json.loads(json.dumps(historical["rounds"][-1]["findings"])),
+ "result": "Not Mergeable",
"feedback_mode": "Change Request"})
+ actual = {**current, "reviews": historical["reviews"] +
current["reviews"],
+ "rounds": historical["rounds"] + current["rounds"],
"result": "Not Mergeable", "feedback_mode": "Change Request"}
+ self.assertIn("Result-review evidence is stale",
ledger.validate_session(actual))
+ actual["rounds"][-1]["findings"][0]["evidence"] =
current["rounds"][0]["report_reviews"][0]["evidence"]
+ self.assertEqual([], ledger.validate_session(actual))
+
+ def test_session_reuses_unchanged_candidate_and_base_proof(self):
+ self.git("add", self.source_path)
+ self.git("commit", "-qm", "Synthetic reviewed candidate")
+ historical = self.create_session([[("F1", "confirmed")], []],
committed=True)
+ report =
json.loads(Path(historical["reviews"][0]["path"]).read_text(encoding="utf-8"))
+ actual_ledger = ledger.read_ledger(report["ledger"])
+ anchor = ledger.capture_anchor({"side": "base", "path":
self.source_path, "start_line": 2, "end_line": 2}, actual_ledger)
+ actual_ledger["findings"][0]["anchors"].append(anchor)
+ self.update_session_basis(historical, 0, actual_ledger,
json.loads(self.basis_file.read_text(encoding="utf-8")))
+ historical["rounds"][0]["findings"][0]["evidence"].append({"reviewer":
"review-1", "anchor": anchor})
+ (self.repo / "Rules.md").write_text("Use the owning public boundary
and preserve the original task baseline.\n", encoding="utf-8")
+ self.git("add", "Rules.md")
+ self.git("commit", "-qm", "Synthetic changed rules with unchanged
candidate evidence")
+ current = self.create_session([[], []], review_start=2, committed=True)
+ current["rounds"][0].update({"findings":
json.loads(json.dumps(historical["rounds"][-1]["findings"])),
+ "result": "Not Mergeable",
"feedback_mode": "Change Request"})
+ actual = {**current, "reviews": historical["reviews"] +
current["reviews"],
+ "rounds": historical["rounds"] + current["rounds"],
"result": "Not Mergeable", "feedback_mode": "Change Request"}
+ self.assertEqual([], ledger.validate_session(actual))
+
+ def test_session_accepts_historical_pair_stages_after_rule_change(self):
+ historical = self.create_session([[], [], [], []])
+
historical["rounds"][1]["report_reviews"].extend(historical["rounds"].pop()["report_reviews"])
+ (self.repo / "Rules.md").write_text("Use an initial pair followed by
one fresh assessment per later stage.\n", encoding="utf-8")
+ current = self.create_session([[], []], review_start=4)
+ actual = {**current, "reviews": historical["reviews"] +
current["reviews"],
+ "rounds": historical["rounds"] + current["rounds"]}
+ self.assertEqual([], ledger.validate_session(actual))
+
+ def
test_session_rechecks_relocated_source_across_current_evidence_sets(self):
+ historical = self.create_session([[("F1", "confirmed")], []])
+ report =
json.loads(Path(historical["reviews"][0]["path"]).read_text(encoding="utf-8"))
+ actual_ledger = ledger.read_ledger(report["ledger"])
+ anchor = ledger.capture_anchor({"side": "source", "origin":
"attempt:target-version", "path": str(self.attempt_log)}, actual_ledger)
+ actual_ledger["findings"][0]["anchors"].append(anchor)
+ self.update_session_basis(historical, 0, actual_ledger,
json.loads(self.basis_file.read_text(encoding="utf-8")))
+ historical["rounds"][0]["findings"][0]["evidence"] = [{"reviewer":
"review-1", "anchor": anchor}]
+ relocated = self.private_root / "relocated-attempt.log"
+ self.attempt_log.rename(relocated)
+ (self.repo / "Rules.md").write_text("Use declared source origins
across all current independent assessments.\n", encoding="utf-8")
+ basis = json.loads(self.basis_file.read_text(encoding="utf-8"))
+ next(each for each in basis["sources"] if "attempt:target-version" ==
each["origin"])["path"] = str(relocated)
+ self.basis_file.write_text(json.dumps(basis), encoding="utf-8")
+ for index, changed in enumerate((False, True)):
+ with self.subTest(changed=changed):
+ if changed:
+ relocated.write_text("The current source disproves the
original external fact.\n", encoding="utf-8")
+ current = self.create_session([[], []], review_start=2 + index
* 2)
+ last_report =
json.loads(Path(current["reviews"][-1]["path"]).read_text(encoding="utf-8"))
+ last_ledger = ledger.read_ledger(last_report["ledger"])
+ independent_basis = {**basis, "sources": [each for each in
basis["sources"] if "attempt:target-version" != each["origin"]]}
+ self.update_session_basis(current, 1, last_ledger,
independent_basis)
+ current["rounds"][0].update({"findings":
json.loads(json.dumps(historical["rounds"][-1]["findings"])),
+ "result": "Not Mergeable",
"feedback_mode": "Change Request"})
+ actual = {**current, "reviews": historical["reviews"] +
current["reviews"],
+ "rounds": historical["rounds"] + current["rounds"],
"result": "Not Mergeable", "feedback_mode": "Change Request"}
+ if changed:
+ self.assertIn("Result-review evidence is stale",
ledger.validate_session(actual))
+ else:
+ self.assertEqual([], ledger.validate_session(actual))
+
actual["rounds"][-1]["findings"][0]["evidence"][0]["anchor"]["path"] =
str(relocated)
+ self.assert_session_cli_rejected(actual)
+
+ def
test_session_recaptures_historical_source_absent_from_current_declarations(self):
+ historical = self.create_session([[("F1", "confirmed")], []])
+ report =
json.loads(Path(historical["reviews"][0]["path"]).read_text(encoding="utf-8"))
+ actual_ledger = ledger.read_ledger(report["ledger"])
+ anchor = ledger.capture_anchor({"side": "source", "origin":
"attempt:target-version", "path": str(self.attempt_log)}, actual_ledger)
+ actual_ledger["findings"][0]["anchors"].append(anchor)
+ self.update_session_basis(historical, 0, actual_ledger,
json.loads(self.basis_file.read_text(encoding="utf-8")))
+ historical["rounds"][0]["findings"][0]["evidence"] = [{"reviewer":
"review-1", "anchor": anchor}]
+ (self.repo / "Rules.md").write_text("Recheck every retained finding
using available current material.\n", encoding="utf-8")
+ basis = json.loads(self.basis_file.read_text(encoding="utf-8"))
+ basis["sources"] = [each for each in basis["sources"] if
"attempt:target-version" != each["origin"]]
+ self.basis_file.write_text(json.dumps(basis), encoding="utf-8")
+ current = self.create_session([[], []], review_start=2)
+ current["rounds"][0].update({"findings":
json.loads(json.dumps(historical["rounds"][-1]["findings"])),
+ "result": "Not Mergeable",
"feedback_mode": "Change Request"})
+ actual = {**current, "reviews": historical["reviews"] +
current["reviews"],
+ "rounds": historical["rounds"] + current["rounds"],
"result": "Not Mergeable", "feedback_mode": "Change Request"}
+ self.assertEqual([], ledger.validate_session(actual))
+ self.attempt_log.write_text("The material no longer supports the
original finding.\n", encoding="utf-8")
+ self.assertIn("Result-review evidence is stale",
ledger.validate_session(actual))
+ self.attempt_log.unlink()
+ self.assert_session_cli_rejected(actual)
+
+ def test_session_accepts_current_coordinator_source_proof(self):
+ actual = self.create_session([[], []])
+ report =
json.loads(Path(actual["reviews"][0]["path"]).read_text(encoding="utf-8"))
+ actual_ledger = ledger.read_ledger(report["ledger"])
+ anchor = ledger.capture_anchor({"side": "source", "origin":
"attempt:target-version", "path": str(self.attempt_log)}, actual_ledger)
+ actual["rounds"][0]["findings"] = [{
+ "id": "coordinator-candidate", "members": [], "status":
"withdrawn", "classification": "correctness",
+ "reason": "Current source evidence disproves the suspected
trigger.",
+ "counter_evidence": "The recorded external fact rules out the
alternative path.",
+ "evidence": [{"reviewer": "review-1", "anchor": anchor}],
+ }]
+ self.assertEqual([], ledger.validate_session(actual))
+
def test_session_preserves_real_incomplete_evidence(self):
actual = self.create_session([[("G1", "review-incomplete-gap")],
[("G1", "review-incomplete-gap")]])
self.assertEqual([], ledger.validate_session(actual))
@@ -940,10 +1105,8 @@ class ReviewLedgerTest(unittest.TestCase):
redirect_stdout(StringIO()),
redirect_stderr(StringIO()):
self.assertEqual(1, ledger.main())
- def create_session(self, review_findings, review_start=0):
- if 2 == len(review_findings):
- review_findings = review_findings * 2
- original = self.create_valid_ledger()
+ def create_session(self, review_findings, review_start=0, committed=False):
+ original = self.create_valid_ledger(committed)
actual = {"reviews": [], "rounds": [], "result": "Mergeable",
"feedback_mode": ""}
findings = {}
report_reviews = []
@@ -972,7 +1135,7 @@ class ReviewLedgerTest(unittest.TestCase):
"reason":
"Checked the triggering path and independent fix boundary.",
"counter_evidence": "Checked the strongest supported counterexample.",
"evidence": evidence})
disposition["members"].append({"reviewer": reviewer,
"finding": finding["id"]})
- if index % 2:
+ if index >= 1:
round_statuses = {finding["status"] for finding in
findings.values()}
result = "Review Incomplete" if "review-incomplete-gap" in
round_statuses else "Not Mergeable" if "confirmed" in round_statuses else
"Mergeable"
actual["rounds"].append({"report_reviews": report_reviews,
"findings": json.loads(json.dumps(list(findings.values()))),
@@ -1010,7 +1173,7 @@ class ReviewLedgerTest(unittest.TestCase):
def initialize_ledger(self, committed=False):
arguments = ["init", "--repo-root", str(self.repo), "--pr",
"synthetic-review", "--base-ref", self.base_sha,
- "--head-ref", "HEAD", "--review-basis",
str(self.basis_file)]
+ "--head-ref", self.git("rev-parse", "HEAD").strip() if
committed else "HEAD", "--review-basis", str(self.basis_file)]
if not committed:
arguments.extend(["--candidate-files", str(self.candidate_files)])
output = StringIO()