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 1618dec2ef3 Enforce independent paired reviews and substantive result
reconciliation (#40217)
1618dec2ef3 is described below
commit 1618dec2ef3c8a02c5e6d0d08b381e8754b266fc
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Oct 18 14:31:13 2026 +0800
Enforce independent paired reviews and substantive result reconciliation
(#40217)
---
.codex/harness/agents/cases.toml | 84 ++++++-
.codex/skills/review-pr/SKILL.md | 64 ++++-
.codex/skills/review-pr/scripts/review_ledger.py | 155 ++++++++++++
.../skills/review-pr/tests/test_review_ledger.py | 277 +++++++++++++++++++++
4 files changed, 573 insertions(+), 7 deletions(-)
diff --git a/.codex/harness/agents/cases.toml b/.codex/harness/agents/cases.toml
index d24ee1a6066..0f8f82aad64 100644
--- a/.codex/harness/agents/cases.toml
+++ b/.codex/harness/agents/cases.toml
@@ -3597,7 +3597,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", statements = ["For a proven
previous-review miss, contradictory assessments on an unchanged basis, a
triggered high-risk review criterion, or an explicit user request for
independent review, obtain an independent read-only assessment through this
workflow and the Completion Gate using the current basis and complete effective
candidate without the previous verdict or an expected finding count.", "Never
use a previous Formal Review result as\nevi [...]
+policy_assertions = [{source = "review-pr", statements = ["Give each reviewer
the current requirements and their provenance, complete candidate inventory and
relevant raw evidence without the author's full session history, previous
assessments, expected verdict or expected finding count.", "Never use a
previous Formal Review result as\nevidence or as a conclusion to match.", "If
at least one candidate passes the Finding Proof Gate, use `Not Mergeable`\n
with `Feedback Mode: Change Requ [...]
[[cases]]
id = "review_ledger_reuses_facts_without_inheriting_completion"
@@ -4163,4 +4163,84 @@ 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.", "For a proven previous-review miss, contradictory assessments on
an unchanged basis, a triggered high-risk r [...]
+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.", "Give each reviewer the current requirements and their
provenance, complete candidate inventory and relevant [...]
+
+[[cases]]
+id = "review_pair_isolation_and_single_pass_roles"
+group = "review-pr independent review coordination"
+description = "Requires isolated complete reviews while preventing recursive
pairs and conclusion sharing."
+phase = "read-only independent-review coordination 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 top-level Formal Review has an
unchanged, fully established basis. Choose A=two-independent-assessments or
A=one-assessment. B is a reviewer assigned one independent assessment in that
pair; choose B=one-complete-workflow or B=start-another-pair. Packet P contains
only current requirements with [...]
+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=two-independent-assessments",
"B=one-complete-workflow", "packet=P", "C=share-raw-evidence",
"D=exclude-prior-completion", "E=report-unfinished",
"F=two-independent-assessments", "G=keep-finding-scope"]
+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", 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 makes an over [...]
+
+[[cases]]
+id = "matching_review_pair_requires_substantive_result_review"
+group = "review-pr independent review coordination"
+description = "Rechecks matching reports and compares normalized complete
issues instead of matching counts."
+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 [...]
+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"]
+critical = true
+policy_binding_profile = "review-pr"
+policy_assertions = [{source = "review-pr", 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 independent fix boun [...]
+
+[[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."
+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 [...]
+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=not-converged", "Q=accept-latest-pair",
"R=merge-duplicate", "S=reject-mixed-bases"]
+forbidden_summary_terms = ["P=finish-with-A",
"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", 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 repeated matching su [...]
+
+[[cases]]
+id = "review_pair_limit_is_not_missing_evidence"
+group = "review-pr independent review coordination"
+description = "Stops after two pairs without a false verdict and preserves the
separate proven-missing-evidence route."
+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. Choose A=stop-not-converged or A=start-fifth-review, and choose
A_verdict=no-formal-result or [...]
+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=stop-not-converged",
"A_verdict=no-formal-result", "B=adjudicate-and-continue",
"C=review-incomplete", "D=report-missing-scope", "Review Status: Not Converged"]
+forbidden_summary_terms = ["A=start-fifth-review",
"A_verdict=review-incomplete", "B=review-incomplete", "C=mergeable",
"D=claim-completed-dual-review"]
+critical = true
+policy_binding_profile = "review-pr"
+policy_assertions = [{source = "review-pr", statements = ["Permit at most two
pairs and four complete independent assessments for one unchanged review basis,
and never select an arbitrary matching pair across groups.", "If the second
pair still disagrees or contradicts a verified historical disposition, report
`Review Status: Not Converged`, the confirmed facts and unresolved differences
without a formal `Review Result` line or a mergeability conclusion.",
"Disagreement, an unavailable i [...]
diff --git a/.codex/skills/review-pr/SKILL.md b/.codex/skills/review-pr/SKILL.md
index b4650d8a4f2..bf16ec110eb 100644
--- a/.codex/skills/review-pr/SKILL.md
+++ b/.codex/skills/review-pr/SKILL.md
@@ -173,6 +173,39 @@ Map every substantive file to the smallest meaningful
behavior cluster and ident
Triage functional boundaries, ownership and shared contracts, compatibility
and rollback, test validity, concurrency and performance, security and
operations, dependencies, packaging, and generated artifacts. Read only the
triggered sections of [high-risk-review.md](references/high-risk-review.md).
Also read [sql-parser-review.md](references/sql-parser-review.md) for SQL
grammar, visitors, parser tests, syntax documentation, dialect behavior, or
parser baselines.
+## Independent Review Coordination
+
+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 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 each reviewer the current requirements and their provenance, complete
candidate inventory and relevant raw evidence without the author's full session
history, previous assessments, expected verdict or expected finding count.
+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.
+Share only current raw facts and verification evidence whose inputs and
applicable coverage remain valid; never share reviewed-file statuses, candidate
findings, proof conclusions or convergence results between independent
reviewers.
+Preserve every original assessment and ledger without rewriting them to make
the pair agree.
+
+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 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.
+The coordinator's result review is focused verification of the reports and
their evidence, not another complete independent assessment, and mechanical
session validation never replaces it.
+
+Accept a pair only when both original assessments agree after issue
normalization and the coordinator's verified dispositions support that same
complete assessment, including every historical candidate.
+When the first pair disagrees or its matching result is unsupported,
adjudicate the candidates from current evidence and obtain one further
independent pair with a fresh complete review of the same basis.
+Do not give that pair the earlier assessments, candidate list, dispositions or
a target conclusion; provide any newly verified raw evidence without the prior
conclusions.
+Permit at most two pairs and four complete independent assessments for one
unchanged review basis, and never select an arbitrary matching pair across
groups.
+If the second pair still disagrees or contradicts a verified historical
disposition, report `Review Status: Not Converged`, the confirmed facts and
unresolved differences without a formal `Review Result` line or a mergeability
conclusion.
+Disagreement, an unavailable isolation mechanism and exhausted review attempts
do not satisfy the Review Incomplete Proof Gate.
+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.
+
+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.
+That proven scope failure may return the existing `Review Incomplete` report
without a session, but must not claim completed dual coverage or a mergeability
conclusion.
+
## Review Workflow
Apply this workflow to the canonical review basis without letting output mode
or a previous result influence the assessment:
@@ -182,9 +215,6 @@ Reuse only facts and verification results whose
current-candidate validity is es
Before repeating evidence acquisition, consult the current ledger's sources,
block coverage, candidate findings and verification material, and reuse facts
only while their supporting inputs and applicable coverage remain valid.
All three discovery lenses may share the same current source material, but
each must still cover the complete candidate and sharing material does not
replace the convergence pass.
-For a proven previous-review miss, contradictory assessments on an unchanged
basis, a triggered high-risk review criterion, or an explicit user request for
independent review, obtain an independent read-only assessment through this
workflow and the Completion Gate using the current basis and complete effective
candidate without the previous verdict or an expected finding count.
-Give the independent reviewer the current requirements and their provenance,
complete candidate inventory and relevant raw evidence rather than the author's
full session history.
-
1. Complete `Repository Code Policy References`, then establish the
authoritative effective-candidate scope and applicable requirements.
2. Complete the Problem and Project Commitment Gate and establish the accepted
behavior boundary.
3. Confirm the selected review focus and admissible evidence.
@@ -211,11 +241,12 @@ Apply the Completion Gate to every Formal Review and to a
discussion reply that
- Complete the Mandatory Style Verification Gate against the latest effective
candidate for every public PR or PR-backed local candidate.
- Leave no unresolved gap that could change the blocker set and require a
latest-candidate convergence pass with zero new independent candidates.
- Use `scripts/build_review_inventory.py --format json` when local refs are
available; its Markdown is only a bounded summary.
-- Use `scripts/review_ledger.py` for multi-file, high-risk, or omission-prone
review; it accounts for review state but does not judge correctness.
+- Use `scripts/review_ledger.py` for each independent assessment; it accounts
for review state but does not judge correctness.
- Initialize a new ledger for each round that requires one, from the full
current inventory with pending file statuses and no inherited review passes.
- Use the existing private system temporary directory and record the exact
ledger path returned by initialization for this review.
- Initialize with `--review-basis <JSON>` containing the selected focus and
readable sources identified by role and origin, so the ledger binds their
actual content versions to the effective candidate, original baseline and
authoritative files.
- Record each substantive changed block's behavior, applicable risk axes and
requirement origins, strongest counter-evidence and current source anchors with
`mark-file --coverage <JSON>`, and attach finding anchors with `add-finding
--anchors <JSON>`.
+- Record each evidenced `Needs Discussion` condition as a `confirmed` ledger
issue about the project commitment or required decision, using `origin`,
`evidence`, `full_path`, `necessity` and `scope_proof` to prove that
requirement and `fix_boundary` for its specific decision boundary; do not
invent a code defect.
- For each newly introduced local override, compatibility exception or
parallel mechanism, use the existing basis sources and block `requirements`,
`counter_evidence` and `anchors` to record its constraint origins, verified
limits of direct reuse and necessity conclusion separately from behavior
correctness.
- Keep author-only choices and unverified assumptions as `evidence` sources.
- Record partial coverage with pending files and keep unproved findings as
candidates; recording evidence does not establish a completed review.
@@ -225,10 +256,32 @@ Apply the Completion Gate to every Formal Review and to a
discussion reply that
- Mutate one ledger sequentially; do not run ledger commands concurrently.
- Keep ledger data private and remove only the exact current-review ledger.
- Before returning a completed review result, form a self-contained report and
automatically run `scripts/review_ledger.py cleanup --ledger <recorded-ledger>`
for the recorded current-review instance, including Mergeable, Not Mergeable
and Review Incomplete results.
+- The coordinator retains the owned review ledgers and input materials until
result review and session validation finish, then applies the exact-path
cleanup rules to each recorded instance and its owned session materials,
including an unconverged session.
- Before cleaning the ledger, remove only temporary input materials created
and explicitly recorded for this review, preserving declared sources owned by
the repository, the user or another review.
- On a handled failure or cancellation, attempt the same cleanup for resources
whose ownership is established, preserve the original failure and report any
cleanup failure or remaining resources.
- Keep the report independent of temporary files and report cleanup failures
without changing the review assessment.
+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.
+Each reviewer writes one private original result JSON containing `reviewer`,
the exact `ledger` path, `ledger_sha256`, `result` and `feedback_mode` after
completing its ledger.
+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 has this shape, with two or four
`reviews` in consecutive pairs and one `rounds` entry per pair:
+
+```json
+{"reviews": [{"path": "<original-result JSON>", "sha256": "<original-result
SHA256>"}], "rounds": [{"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":
"<identity>", "finding": "<ledger finding ID>"}], "status": "<finding status>",
"reason": "<disposition and [...]
+```
+
+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.
+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.
+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`.
+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.
+The final pair's original result, feedback mode, normalized confirmed issues
and evidence gaps must agree with each other and with the final round's
complete historical dispositions and proposed session result.
+If session validation fails, inspect the exact failure and correct only an
unsupported record or missing evidence; never rewrite an original assessment or
change issue mappings merely to obtain a pass.
+Only the coordinator emits the final report under the Output Contract after
validation succeeds.
+
Use these JSON shapes in local files passed to the arguments, replacing the
example values with current evidence and block IDs from the initialized ledger:
Sources use `rule`, `requirement` or `evidence` roles; `--anchors` takes an
anchor-array file shaped like `anchors` below.
@@ -254,7 +307,7 @@ When confirmed blockers coexist with a proven gap that
could hide more blockers,
## Formal Decision Contract
Map the canonical assessment to Formal Review only after the Completion Gate
-evaluation:
+evaluation and Independent Review Coordination:
1. If the gate fails because a gap satisfies the Review Incomplete Proof Gate,
use `Review Incomplete`, even when some blockers are already confirmed.
2. If the Mandatory Style Verification Gate is incomplete for any applicable
PR-impact file, use `Review Incomplete`, even when some blockers are already
confirmed.
@@ -317,6 +370,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.
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/scripts/review_ledger.py
b/.codex/skills/review-pr/scripts/review_ledger.py
index 4d68a4069a1..e330cc859b2 100755
--- a/.codex/skills/review-pr/scripts/review_ledger.py
+++ b/.codex/skills/review-pr/scripts/review_ledger.py
@@ -742,6 +742,158 @@ def cmd_validate_incomplete(args: argparse.Namespace) ->
int:
return 0
+def read_review_record(reference: Any) -> dict[str, Any]:
+ if not isinstance(reference, dict) or any(not has_text(reference.get(key))
for key in ("path", "sha256")):
+ raise RuntimeError("Review references require a path and original
content hash")
+ content = Path(reference["path"]).read_bytes()
+ if hashlib.sha256(content).hexdigest() != reference["sha256"]:
+ raise RuntimeError("An original review result changed after
collection")
+ review = json.loads(content)
+ if not isinstance(review, dict) or any(not has_text(review.get(key)) for
key in ("reviewer", "ledger", "ledger_sha256")):
+ raise RuntimeError("Review results require an isolated reviewer
identity and original ledger reference")
+ validate_review_decision(review)
+ ledger_file =
ensure_safe_ledger_file(resolve_ledger_file(review["ledger"]))
+ if hashlib.sha256(ledger_file.read_bytes()).hexdigest() !=
review["ledger_sha256"]:
+ raise RuntimeError("An original review ledger changed after
collection")
+ return {**review, "path": str(Path(reference["path"]).resolve()),
"ledger_file": str(ledger_file), "ledger": read_ledger(ledger_file)}
+
+
+def validate_review_decision(assessment: dict[str, Any]) -> None:
+ result, feedback = assessment.get("result"),
assessment.get("feedback_mode")
+ if not isinstance(result, str) or not isinstance(feedback, str):
+ raise RuntimeError("Review result and feedback mode must be strings")
+ if result not in {"Mergeable", "Not Mergeable", "Review Incomplete"} or
feedback not in {"", "Change Request", "Needs Discussion"}:
+ raise RuntimeError("Review decisions require a supported result and
feedback mode")
+ if ("Not Mergeable" == result) != bool(feedback):
+ raise RuntimeError("Only Not Mergeable decisions require a feedback
mode")
+
+
+def review_input_identity(ledger: dict[str, Any]) -> str:
+ basis = ledger["basis"]
+ return digest({"focus": basis["focus"], "baseline":
basis["baseline"]["tree"], "delta": basis["delta"],
+ "requirements": sorted((each["role"], each["origin"],
each["mode"], each["sha256"]) for each in basis["sources"]
+ if each["role"] in {"rule",
"requirement"})})
+
+
+def result_review_errors(proof: Any, reviews: dict[str, dict[str, Any]],
own_reviewer: str = "") -> 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"]:
+ raise RuntimeError("Result review requires current source evidence")
+ errors = []
+ for entry in proof["evidence"]:
+ if not isinstance(entry, dict) or not has_text(entry.get("reviewer"))
or not isinstance(entry.get("anchor"), dict):
+ raise RuntimeError("Result-review evidence requires a reviewer and
captured source anchor")
+ reviewer = entry["reviewer"]
+ if reviewer not in reviews or own_reviewer and reviewer !=
own_reviewer:
+ errors.append("Result-review evidence does not belong to the
reviewed original result")
+ continue
+ anchor = entry["anchor"]
+ validate_anchor_shape(anchor, captured=True)
+ if capture_anchor(anchor, reviews[reviewer]["ledger"]) != 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"]) not in {2, 4}:
+ raise RuntimeError("A review session requires one or two 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)
+ 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}):
+ raise RuntimeError("Independent reviews must have distinct
identities, results and ledgers")
+ errors = []
+ if len({review_input_identity(each["ledger"]) for each in records}) != 1:
+ errors.append("Review candidates, original baselines, requirements or
focus differ")
+ source_versions = {}
+ for record in records:
+ ledger = record["ledger"]
+ errors.extend(validate_ledger(ledger, incomplete_result="Review
Incomplete" == record["result"]))
+ 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")
+ for source in ledger["basis"]["sources"]:
+ version = (source["mode"], source["sha256"])
+ if source["origin"] in source_versions and
source_versions[source["origin"]] != version:
+ errors.append("Independent reviews disagree on a shared source
version")
+ source_versions[source["origin"]] = version
+ known_reviews = {}
+ original_findings = set()
+ prior_ids = set()
+ final_groups = []
+ final_members = {}
+ 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")
+ pair = records[index * 2:index * 2 + 2]
+ known_reviews.update((each["reviewer"], each) for each in pair)
+ for record in pair:
+ original_findings.update((record["reviewer"], each["id"]) for each
in record["ledger"]["findings"])
+ audited_reviewers = []
+ for report_review in review_round["report_reviews"]:
+ if not isinstance(report_review, dict) or not
has_text(report_review.get("reviewer")):
+ 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))
+ if Counter(audited_reviewers) != Counter(each["reviewer"] for each in
pair):
+ errors.append("Both original reports require substantive result
review, even when they agree")
+ groups, members = {}, {}
+ for finding in review_round["findings"]:
+ if not isinstance(finding, dict) or not
has_text(finding.get("id")) or not isinstance(finding.get("status"), str):
+ raise RuntimeError("Finding dispositions require a stable
identity and supported status")
+ if finding["status"] not in FINDING_STATUSES:
+ raise RuntimeError("Finding dispositions require a stable
identity and supported status")
+ if not isinstance(finding.get("members"), list):
+ raise RuntimeError("Finding dispositions require explicit
original finding mappings")
+ if finding["id"] in groups:
+ errors.append("Canonical finding identities are duplicated")
+ groups[finding["id"]] = finding
+ errors.extend(result_review_errors(finding, known_reviews))
+ 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")
+ key = (member["reviewer"], member["finding"])
+ if key in members:
+ errors.append("An original finding has multiple
dispositions")
+ members[key] = finding["id"]
+ if set(members) != original_findings:
+ errors.append("Every historical candidate requires exactly one
explicit disposition")
+ 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
+ if any(each["status"] == "candidate" for each in final_groups):
+ errors.append("Result-review candidates remain unresolved")
+ final_confirmed = {each["id"] for each in final_groups if each["status"]
== "confirmed"}
+ final_gaps = {each["id"] for each in final_groups if each["status"] ==
"review-incomplete-gap"}
+ expected_result = "Review Incomplete" if final_gaps else "Not Mergeable"
if final_confirmed else "Mergeable"
+ if session["result"] != expected_result:
+ errors.append("The final decision contradicts the complete historical
dispositions")
+ expected = (session["result"], session["feedback_mode"], final_confirmed,
final_gaps)
+ for record in records[-2:]:
+ finding_sets = []
+ for status in ("confirmed", "review-incomplete-gap"):
+ finding_sets.append({final_members[(record["reviewer"],
each["id"])] for each in record["ledger"]["findings"]
+ if each["status"] == status and
(record["reviewer"], each["id"]) in final_members})
+ if (record["result"], record["feedback_mode"], *finding_sets) !=
expected:
+ errors.append("The latest pair does not agree with the result
review and all historical findings")
+ return errors
+
+
+def cmd_validate_session(args: argparse.Namespace) -> int:
+ errors =
validate_session(json.loads(Path(args.session).read_text(encoding="utf-8")))
+ if errors:
+ 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")
+ return 0
+
+
def cmd_status(args: argparse.Namespace) -> int:
ledger = read_ledger(args.ledger)
file_counts = Counter(each["status"] for each in ledger["files"])
@@ -824,6 +976,9 @@ def build_parser() -> argparse.ArgumentParser:
validate_incomplete = subparsers.add_parser("validate-incomplete",
help="Validate a proven Review Incomplete result")
validate_incomplete.add_argument("--ledger", required=True, help="Ledger
file or directory")
validate_incomplete.set_defaults(func=cmd_validate_incomplete)
+ validate_session_parser = subparsers.add_parser("validate-session",
help="Validate two blind reviews and substantive result review")
+ validate_session_parser.add_argument("--session", required=True,
help="JSON original result references and result-review dispositions")
+ validate_session_parser.set_defaults(func=cmd_validate_session)
status = subparsers.add_parser("status", help="Print compact coverage
status")
status.add_argument("--ledger", required=True, help="Ledger file or
directory")
status.set_defaults(func=cmd_status)
diff --git a/.codex/skills/review-pr/tests/test_review_ledger.py
b/.codex/skills/review-pr/tests/test_review_ledger.py
index 72594645100..09aa7d23acd 100644
--- a/.codex/skills/review-pr/tests/test_review_ledger.py
+++ b/.codex/skills/review-pr/tests/test_review_ledger.py
@@ -15,6 +15,7 @@
# limitations under the License.
#
+import hashlib
import json
import os
import subprocess
@@ -641,6 +642,282 @@ class ReviewLedgerTest(unittest.TestCase):
actual["findings"] = [finding]
self.assertIn("G1 incomplete gap is missing real unavailability-proof
anchors", ledger.validate_ledger(actual, incomplete_result=True))
+ def test_session_requires_each_report_evidence_review(self):
+ original = self.create_session([[], []])
+ self.assertEqual([], ledger.validate_session(original))
+ for missing in ("report", "reason", "counter_evidence", "evidence",
"current_anchor", "own_evidence", "valid_range"):
+ with self.subTest(missing=missing):
+ actual = json.loads(json.dumps(original))
+ report_reviews = actual["rounds"][0]["report_reviews"]
+ if "report" == missing:
+ report_reviews.pop()
+ elif "current_anchor" == missing:
+ report_reviews[0]["evidence"][0]["anchor"]["range_sha256"]
= "0" * 64
+ elif "own_evidence" == missing:
+ report_reviews[0]["evidence"][0]["reviewer"] = "review-2"
+ elif "valid_range" == missing:
+ 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"}:
+ self.assert_session_cli_rejected(actual)
+ else:
+ self.assertTrue(ledger.validate_session(actual))
+
+ def test_session_normalizes_matching_root_causes(self):
+ actual = self.create_session([[("F1", "confirmed")], [("F2",
"confirmed")]])
+ findings = actual["rounds"][0]["findings"]
+ findings[0]["members"].extend(findings.pop()["members"])
+ self.assertEqual([], ledger.validate_session(actual))
+
+ def test_session_accepts_verified_maintainer_decision(self):
+ (self.repo / "Rules.md").write_text("The candidate's project
commitment requires an explicit maintainer decision before merge.\n",
encoding="utf-8")
+ actual = self.create_session([[("F1", "confirmed")], [("F2",
"confirmed")]])
+ for index, reference in enumerate(actual["reviews"]):
+ report =
json.loads(Path(reference["path"]).read_text(encoding="utf-8"))
+ actual_ledger = ledger.read_ledger(report["ledger"])
+ rule_anchor = ledger.capture_anchor({"side": "source", "origin":
"repository:Rules.md", "path": "Rules.md", "start_line": 1, "end_line": 1},
actual_ledger)
+ actual_ledger["findings"][0].update({
+ "origin": "repository:Rules.md", "fix_boundary":
"maintainer-decision:project-commitment",
+ "reason": "The evidenced project commitment needs a maintainer
decision.",
+ "necessity": "The project rule reserves acceptance of this
commitment to maintainers.",
+ "evidence": ["Rules.md explicitly requires a maintainer
decision before merge."],
+ "full_path": ["Reviewed candidate to project commitment to
required maintainer decision."],
+ "counter_evidence": ["No existing project rule delegates this
decision to the reviewer."],
+ "scope_proof": "The reviewed candidate is governed by the
project commitment rule.", "anchors": [rule_anchor],
+ })
+ ledger_file = Path(report["ledger"])
+ ledger.write_ledger(ledger_file, actual_ledger)
+ report.update({"ledger_sha256":
hashlib.sha256(ledger_file.read_bytes()).hexdigest(), "feedback_mode": "Needs
Discussion"})
+ self.save_session_report(actual, index, report)
+ findings = actual["rounds"][0]["findings"]
+ findings[0]["members"].extend(findings.pop()["members"])
+ findings[0].update({"id": "project-commitment-decision", "reason":
"The confirmed project rule requires a maintainer decision.",
+ "counter_evidence": "Reviewer agreement cannot
replace the required maintainer decision.",
+ "evidence": [{"reviewer": "review-2", "anchor":
rule_anchor}]})
+ actual["feedback_mode"] = "Needs Discussion"
+ self.assertEqual([], ledger.validate_session(actual))
+
+ def test_session_preserves_all_candidate_dispositions(self):
+ original = self.create_session([[("F1", "withdrawn"), ("F2",
"non-blocking"), ("F3", "out-of-scope")], []])
+ self.assertEqual([], ledger.validate_session(original))
+ for missing in ("member", "finding", "disposition"):
+ with self.subTest(missing=missing):
+ actual = json.loads(json.dumps(original))
+ findings = actual["rounds"][0]["findings"]
+ if "member" == missing:
+ findings[0]["members"] = []
+ elif "finding" == missing:
+ findings.pop()
+ else:
+ findings[0]["status"] = "candidate"
+ self.assertTrue(ledger.validate_session(actual))
+
+ def
test_session_rejects_same_count_with_different_findings_or_feedback(self):
+ actual = self.create_session([[("F1", "confirmed")], [("F2",
"confirmed")]])
+ self.assertTrue(ledger.validate_session(actual))
+
actual["rounds"][0]["findings"][0]["members"].extend(actual["rounds"][0]["findings"].pop()["members"])
+ report =
json.loads(Path(actual["reviews"][1]["path"]).read_text(encoding="utf-8"))
+ report["feedback_mode"] = "Needs Discussion"
+ self.save_session_report(actual, 1, report)
+ self.assertTrue(ledger.validate_session(actual))
+
+ def test_session_allows_second_pair_after_disagreement(self):
+ actual = self.create_session([[("F1", "confirmed")], [], [("F1",
"confirmed")], [("F1", "confirmed")]])
+ self.assertEqual([], ledger.validate_session(actual))
+
+ def test_session_preserves_finding_discovered_during_result_review(self):
+ original = self.create_session([[], [], [("F1", "confirmed")], [("F1",
"confirmed")]])
+ original["rounds"][0]["findings"].append({
+ "id": "normalized-F1", "members": [], "status": "confirmed",
+ "reason": "The report evidence review found an unsupported
triggering path.",
+ "counter_evidence": "The supported alternative does not protect
this path.",
+ "evidence": original["rounds"][0]["report_reviews"][0]["evidence"],
+ })
+ 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))
+ 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):
+ original = self.create_session([[("F1", "confirmed")], [("F1",
"confirmed"), ("F2", "confirmed")],
+ [("F1", "confirmed")], [("F1",
"confirmed")]])
+ self.assertTrue(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))
+ actual["rounds"][-1]["findings"][-1]["status"] = "withdrawn"
+ self.assertEqual([], ledger.validate_session(actual))
+
+ def test_session_limits_complete_review_pairs(self):
+ original = self.create_session([[("F1", "confirmed")], [], [("F1",
"confirmed")], [("F1", "confirmed")]])
+ self.assertEqual([], ledger.validate_session(original))
+ for count in (0, 1, 3, 6):
+ 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_rejects_reused_or_modified_original_records(self):
+ original = self.create_session([[], []])
+ original_report =
Path(original["reviews"][1]["path"]).read_text(encoding="utf-8")
+ for invalid in ("report_path", "reviewer", "ledger_path",
"report_hash", "ledger_hash"):
+ with self.subTest(invalid=invalid):
+ actual = json.loads(json.dumps(original))
+ report = json.loads(original_report)
+ if "report_path" == invalid:
+ actual["reviews"][1] = actual["reviews"][0]
+ elif "report_hash" == invalid:
+ actual["reviews"][1]["sha256"] = "0" * 64
+ else:
+ if "reviewer" == invalid:
+ report["reviewer"] = "review-1"
+ actual["rounds"][0]["report_reviews"][1]["reviewer"] =
"review-1"
+
actual["rounds"][0]["report_reviews"][1]["evidence"][0]["reviewer"] = "review-1"
+ elif "ledger_path" == invalid:
+ report["ledger"] =
json.loads(Path(actual["reviews"][0]["path"]).read_text(encoding="utf-8"))["ledger"]
+ report["ledger_sha256"] =
hashlib.sha256(Path(report["ledger"]).read_bytes()).hexdigest()
+ else:
+ report["ledger_sha256"] = "0" * 64
+ self.save_session_report(actual, 1, report)
+ self.assert_session_cli_rejected(actual)
+
Path(original["reviews"][1]["path"]).write_text(original_report,
encoding="utf-8")
+
+ def test_session_requires_same_current_review_basis(self):
+ actual = self.create_session([[], []])
+ report =
json.loads(Path(actual["reviews"][1]["path"]).read_text(encoding="utf-8"))
+ original = ledger.read_ledger(report["ledger"])
+ for changed in ("focus", "rule", "requirement", "evidence_origin"):
+ with self.subTest(changed=changed):
+ actual_ledger = json.loads(json.dumps(original))
+ basis = json.loads(self.basis_file.read_text(encoding="utf-8"))
+ if "focus" == changed:
+ basis["focus"] = "CI Review"
+ else:
+ role = "evidence" if "evidence_origin" == changed else
changed
+ source = next(each for each in basis["sources"] if role ==
each["role"])
+ alternate = self.private_root / (changed + ".txt")
+ alternate.write_text("A different reviewed source
contract.\n", encoding="utf-8")
+ source["path"] = str(alternate)
+ self.update_session_basis(actual, 1, actual_ledger, basis)
+ self.assertEqual([], ledger.validate_ledger(actual_ledger))
+ self.assertTrue(ledger.validate_session(actual))
+ self.update_session_basis(actual, 1, original,
json.loads(self.basis_file.read_text(encoding="utf-8")))
+ (self.repo /
self.source_path).write_text("first\nchanged_after_review\nthird\nfourth\nfifth\nnew_second\nlast\n",
encoding="utf-8")
+ self.assertTrue(ledger.validate_session(actual))
+
+ def test_session_accepts_different_discovered_evidence(self):
+ actual = self.create_session([[], []])
+ report =
json.loads(Path(actual["reviews"][1]["path"]).read_text(encoding="utf-8"))
+ actual_ledger = ledger.read_ledger(report["ledger"])
+ basis = json.loads(self.basis_file.read_text(encoding="utf-8"))
+ evidence = self.private_root / "additional-evidence.txt"
+ evidence.write_text("Additional independent discovery evidence.\n",
encoding="utf-8")
+ basis["sources"].append({"role": "evidence", "origin":
"discovery:second-review", "path": str(evidence)})
+ self.update_session_basis(actual, 1, actual_ledger, basis)
+ 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))
+ actual = self.create_session([[], []])
+ actual["result"] = "Review Incomplete"
+ self.assertTrue(ledger.validate_session(actual))
+
+ def
test_session_cli_validates_without_writing_and_preserves_legacy_validation(self):
+ actual = self.create_session([[], []])
+ session_file = self.private_root / "session.json"
+ session_file.write_text(json.dumps(actual), encoding="utf-8")
+ original_files = {path: path.read_bytes() for path in
self.private_root.rglob("*.json")}
+ for command in (["validate-session", "--session", str(session_file)],
+ ["validate", "--ledger",
json.loads(Path(actual["reviews"][0]["path"]).read_text(encoding="utf-8"))["ledger"]]):
+ with self.subTest(command=command[0]), patch.object(sys, "argv",
["review_ledger.py", *command]), redirect_stdout(StringIO()):
+ self.assertEqual(0, ledger.main())
+ self.assertEqual(original_files, {path: path.read_bytes() for path in
original_files})
+ original = json.loads(json.dumps(actual))
+ for invalid in ("unfinished", "shape", "result", "feedback_mode",
"finding_status"):
+ with self.subTest(invalid=invalid):
+ actual = json.loads(json.dumps(original))
+ if "unfinished" == invalid:
+ actual["rounds"][0]["report_reviews"] = []
+ elif "shape" == invalid:
+ actual = {"reviews": "invalid"}
+ elif "finding_status" == invalid:
+ actual["rounds"][0]["findings"] = [{"id": "invalid",
"members": [], "status": []}]
+ else:
+ actual[invalid] = []
+ session_file.write_text(json.dumps(actual), encoding="utf-8")
+ with patch.object(sys, "argv", ["review_ledger.py",
"validate-session", "--session", str(session_file)]), \
+ redirect_stdout(StringIO()),
redirect_stderr(StringIO()):
+ self.assertEqual(1, ledger.main())
+
+ def create_session(self, review_findings):
+ original = self.create_valid_ledger()
+ actual = {"reviews": [], "rounds": [], "result": "Mergeable",
"feedback_mode": ""}
+ findings = {}
+ report_reviews = []
+ for index, each in enumerate(review_findings):
+ reviewer = "review-" + str(index + 1)
+ actual_ledger = json.loads(json.dumps(original))
+ for finding_id, status in each:
+ finding = self.create_incomplete_gap() if
"review-incomplete-gap" == status else self.create_confirmed_finding(finding_id)
+ finding.update({"id": finding_id, "status": status,
"fix_boundary": finding_id})
+ actual_ledger["findings"].append(finding)
+ ledger_file = ledger.ledger_root() / reviewer /
ledger.LEDGER_FILE_NAME
+ ledger_file.parent.mkdir(exist_ok=True)
+ ledger.write_ledger(ledger_file, actual_ledger)
+ statuses = {finding["status"] for finding in
actual_ledger["findings"]}
+ result = "Not Mergeable" if "confirmed" in statuses else "Review
Incomplete" if "review-incomplete-gap" in statuses else "Mergeable"
+ report = {"reviewer": reviewer, "ledger": str(ledger_file),
"ledger_sha256": hashlib.sha256(ledger_file.read_bytes()).hexdigest(),
+ "result": result, "feedback_mode": "Change Request" if
"Not Mergeable" == result else ""}
+ actual["reviews"].append({"path": str(self.private_root /
(reviewer + ".json")), "sha256": ""})
+ self.save_session_report(actual, index, report)
+ evidence = [{"reviewer": reviewer, "anchor":
actual_ledger["files"][0]["coverage"][0]["anchors"][0]}]
+ report_reviews.append({"reviewer": reviewer, "reason": "Checked
the report against its owning source contract.",
+ "counter_evidence": "Checked the supported
alternative behavior.", "evidence": evidence})
+ for finding in actual_ledger["findings"]:
+ disposition = findings.setdefault(finding["id"], {"id":
"normalized-" + finding["id"], "members": [], "status": finding["status"],
+ "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:
+ actual["rounds"].append({"report_reviews": report_reviews,
"findings": json.loads(json.dumps(list(findings.values())))})
+ report_reviews = []
+ actual.update({"result": report["result"], "feedback_mode":
report["feedback_mode"]})
+ return actual
+
+ def save_session_report(self, session, index, report):
+ path = Path(session["reviews"][index]["path"])
+ path.write_text(json.dumps(report), encoding="utf-8")
+ session["reviews"][index]["sha256"] =
hashlib.sha256(path.read_bytes()).hexdigest()
+
+ def assert_session_cli_rejected(self, session):
+ session_file = self.private_root / "rejected-session.json"
+ session_file.write_text(json.dumps(session), encoding="utf-8")
+ output = StringIO()
+ with patch.object(sys, "argv", ["review_ledger.py",
"validate-session", "--session", str(session_file)]), \
+ redirect_stdout(output), redirect_stderr(StringIO()):
+ self.assertEqual(1, ledger.main())
+ self.assertNotIn("OK:", output.getvalue())
+
+ def update_session_basis(self, session, index, actual_ledger, basis):
+ basis_file = self.private_root / ("independent-basis-" + str(index) +
".json")
+ basis_file.write_text(json.dumps(basis), encoding="utf-8")
+ actual_ledger["scope"]["review_basis_file"] = str(basis_file)
+ actual_ledger["basis"], _ =
ledger.capture_basis(actual_ledger["scope"])
+ for review_pass in actual_ledger["passes"]:
+ review_pass["basis_fingerprint"] =
ledger.basis_fingerprint(actual_ledger)
+ report =
json.loads(Path(session["reviews"][index]["path"]).read_text(encoding="utf-8"))
+ ledger_file = Path(report["ledger"])
+ ledger.write_ledger(ledger_file, actual_ledger)
+ report["ledger_sha256"] =
hashlib.sha256(ledger_file.read_bytes()).hexdigest()
+ self.save_session_report(session, index, report)
+
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)]