fix(validator): align final-report validator with canonical schema (Closes #698)
Fixes the final-report validator defects from Issue #698 (original lead plus the independent reproduction recorded during the PR #703 formal review, comment 11246): - Legacy fields: the review/merger required-field tables no longer demand 'Pinned reviewed head', 'Scratch worktree used', 'Worktree path', 'Worktree dirty', 'Mutations', 'Next', 'Issue/PR', 'Branch/SHA', or 'Files changed' — names the canonical review-merge-final-report schema forbids or replaces. The canonical names ('Reviewed head SHA', 'Review worktree path/dirty', 'Safe next action', mutation categories) are required instead, with backward-compatible aliases where the schema permits them. - Structured proof: workflow-load helper results are recognized in colon, key=value, and JSON renderings; validation pass proof is accepted anywhere in the Validation field value (for example 'focused 50 passed; full 2665 passed'). - Mutation inference: review mutations are inferred only from authoritative evidence (performed=true and not gated); read-only diagnostics and pre-API rejections no longer count as mutations. - Lease release vs cleanup: canonical reviewer lease release (release tool call or terminal phase=released marker) is lease lifecycle, not post-merge cleanup, and no longer triggers the branch/worktree cleanup checklist; genuine delete/remove claims still require full proof. - Blocked reports: a legitimately blocked run that states an explicit 'Reviewed/Candidate head SHA: none' with no verdict, merge, or started validation owes no head proofs; approval-time and merge-time live-head proofs are demanded only once the corresponding phase begins, and a report that states no head at all still fails closed. - action_log robustness: malformed (non-dict) entries and non-list logs are reported as clear sanitized findings (position and type only, no content echo) instead of crashing with AttributeError; a defective validator rule now fails closed with a sanitized block finding rather than raising a secondary exception. 27 new regression tests, including canonical fixtures modeled on the PR #703 formal-review handoff and the blocked preflight report from the prior #698 reproductions. Two legacy-field test fixtures updated to the canonical schema. Full suite: 2663 passed, 6 skipped, 161 subtests. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,461 @@
|
||||
"""Regression tests for #698: final-report validator vs canonical schema.
|
||||
|
||||
Covers the original #698 lead plus the independent reproduction recorded
|
||||
during the PR #703 formal review (issue #698 comment 11246):
|
||||
|
||||
1. non-dict ``action_log`` entries must fail structured, never crash;
|
||||
2. the validator must not demand legacy fields the canonical schema forbids
|
||||
(``Pinned reviewed head``, ``Scratch worktree used``, ``Worktree path``,
|
||||
``Worktree dirty``, ``Mutations``, ``Next``);
|
||||
3. a legitimately blocked report (``Candidate head SHA: none``, no formal
|
||||
verdict) must not owe approval/merge live-head proofs;
|
||||
4. canonical reviewer lease release must not be misclassified as post-merge
|
||||
cleanup;
|
||||
5. structured workflow-load and validation proof must be recognized;
|
||||
6. review mutations are inferred only from authoritative evidence.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
|
||||
import final_report_validator as frv # noqa: E402
|
||||
import post_merge_cleanup_proof as pmcp # noqa: E402
|
||||
import pr_work_lease as pwl # noqa: E402
|
||||
import review_proofs as rp # noqa: E402
|
||||
from review_final_report_schema import ( # noqa: E402
|
||||
assess_review_final_report_schema,
|
||||
)
|
||||
|
||||
REVIEWED_HEAD = "a" * 40
|
||||
LIVE_HEAD = "a" * 40
|
||||
|
||||
|
||||
def _pr703_style_report(
|
||||
*,
|
||||
decision: str = "request_changes",
|
||||
cleanup_mutations: str = (
|
||||
"released reviewer PR lease via gitea_release_reviewer_pr_lease "
|
||||
"(terminal lease marker phase=released posted)"
|
||||
),
|
||||
) -> str:
|
||||
"""Canonical-schema report modeled on the PR #703 formal review handoff."""
|
||||
return f"""
|
||||
Formal review completed with a REQUEST_CHANGES verdict submitted and read
|
||||
back via the native review API.
|
||||
|
||||
## Controller Handoff
|
||||
|
||||
- Task: review-merge-pr
|
||||
- Repo: Scaled-Tech-Consulting/Gitea-Tools
|
||||
- Role: reviewer
|
||||
- Identity: sysadmin / prgs-reviewer
|
||||
- Active profile: prgs-reviewer
|
||||
- Runtime context: neutral workspace binding
|
||||
- Selected PR: 703
|
||||
- Linked issue: #702 open
|
||||
- Eligibility class: reviewable
|
||||
- Queue ordering policy: oldest eligible first
|
||||
- Inventory pagination proof: has_more=false, total_count=8
|
||||
- Earlier PRs skipped: none
|
||||
- Candidate head SHA: {REVIEWED_HEAD}
|
||||
- Reviewed head SHA: {REVIEWED_HEAD}
|
||||
- Target branch: master
|
||||
- Target branch SHA: {"2" * 40}
|
||||
- Already-landed gate: not landed
|
||||
- Author-safety result: pass (author differs from reviewer)
|
||||
- Prior request-changes state: none
|
||||
- Review worktree used: true
|
||||
- Review worktree path: branches/review-pr-703-independent
|
||||
- Review worktree inside branches: true
|
||||
- Review worktree HEAD state: detached at pinned head
|
||||
- Review worktree dirty before validation: clean
|
||||
- Review worktree dirty after validation: clean
|
||||
- Baseline worktree used: false
|
||||
- Baseline worktree path: none
|
||||
- Files reviewed: 4
|
||||
- Validation: focused 50 passed; related 94 passed; full 2665 passed, 6 skipped
|
||||
- Official validation integrity status: intact
|
||||
- Terminal review mutation: one REQUEST_CHANGES review submitted and read back
|
||||
- Review decision: {decision}
|
||||
- Merge preflight: not run
|
||||
- Merge result: none
|
||||
- Linked issue status: open (live fetch proof: gitea_view_issue)
|
||||
- Main checkout branch: master
|
||||
- Main checkout dirty state: clean
|
||||
- Main checkout updated: false
|
||||
- File edits by reviewer: none
|
||||
- Worktree/index mutations: none
|
||||
- Git ref mutations: git fetch prgs (recorded)
|
||||
- MCP/Gitea mutations: review submission and lease comments only
|
||||
- Review mutations: one formal REQUEST_CHANGES verdict
|
||||
- Merge mutations: none
|
||||
- Cleanup mutations: {cleanup_mutations}
|
||||
- External-state mutations: none
|
||||
- Read-only diagnostics: gitea_view_pr, gitea_get_pr_review_feedback
|
||||
- Blockers: findings F1-F6 recorded on the PR thread
|
||||
- Current status: review complete; author remediation required
|
||||
- Safe next action: author addresses findings and pushes a new head
|
||||
- Safety statement: no merge attempted; no self-review; no root-checkout edits
|
||||
- Workflow-load helper result: workflow_hash=da045d1e1f1f boundary_status=clean
|
||||
- Live head SHA before approval: {LIVE_HEAD}
|
||||
- Pushes occurred during validation: no
|
||||
"""
|
||||
|
||||
|
||||
def _blocked_preflight_report() -> str:
|
||||
"""Blocked-run report modeled on the #702 comment 11164 reproduction."""
|
||||
return """
|
||||
Fresh review preflight stopped before any worktree or validation work.
|
||||
|
||||
## Controller Handoff
|
||||
|
||||
- Task: review-merge-pr
|
||||
- Repo: Scaled-Tech-Consulting/Gitea-Tools
|
||||
- Role: reviewer
|
||||
- Identity: sysadmin / prgs-reviewer
|
||||
- Active profile: prgs-reviewer
|
||||
- Runtime context: stale workspace binding detected
|
||||
- Selected PR: 701
|
||||
- Linked issue: #699 open
|
||||
- Eligibility class: blocked-before-validation
|
||||
- Queue ordering policy: oldest eligible first
|
||||
- Inventory pagination proof: has_more=false, total_count=8
|
||||
- Earlier PRs skipped: none
|
||||
- Candidate head SHA: none
|
||||
- Reviewed head SHA: none
|
||||
- Target branch: master
|
||||
- Target branch SHA: none
|
||||
- Already-landed gate: not run
|
||||
- Author-safety result: not run
|
||||
- Prior request-changes state: none
|
||||
- Review worktree used: false
|
||||
- Review worktree path: none
|
||||
- Review worktree inside branches: not applicable
|
||||
- Review worktree HEAD state: not applicable
|
||||
- Review worktree dirty before validation: not applicable
|
||||
- Review worktree dirty after validation: not applicable
|
||||
- Baseline worktree used: false
|
||||
- Baseline worktree path: none
|
||||
- Files reviewed: 0
|
||||
- Validation: not run
|
||||
- Official validation integrity status: not applicable
|
||||
- Terminal review mutation: none
|
||||
- Review decision: none
|
||||
- Merge preflight: not run
|
||||
- Merge result: none
|
||||
- Linked issue status: open (live fetch proof: gitea_view_issue)
|
||||
- Main checkout branch: master
|
||||
- Main checkout dirty state: clean
|
||||
- Main checkout updated: false
|
||||
- File edits by reviewer: none
|
||||
- Worktree/index mutations: none
|
||||
- Git ref mutations: none
|
||||
- MCP/Gitea mutations: none
|
||||
- Review mutations: none
|
||||
- Merge mutations: none
|
||||
- Cleanup mutations: none
|
||||
- External-state mutations: none
|
||||
- Read-only diagnostics: gitea_view_pr, gitea_get_runtime_context
|
||||
- Blockers: runtime bound to a foreign task worktree; mutation prohibited
|
||||
- Current status: stopped before validation began
|
||||
- Next actor: operator
|
||||
- Next action: repair the runtime workspace binding, then rerun the full
|
||||
review workflow in a fresh reviewer session
|
||||
- Next prompt: Act as REVIEWER for PR 701 after the operator repairs the
|
||||
runtime binding; acquire the lease before any validation.
|
||||
- Safe next action: operator repairs runtime binding, then a fresh reviewer
|
||||
reruns the full workflow
|
||||
- Safety statement: no lease acquired; no verdict recorded; no source edits
|
||||
- Workflow-load helper result: workflow_hash=da045d1e1f1f boundary_status=clean
|
||||
"""
|
||||
|
||||
|
||||
class TestActionLogRobustness(unittest.TestCase):
|
||||
"""#698 original lead: non-dict action_log must not crash validation."""
|
||||
|
||||
MALFORMED = [
|
||||
"git fetch prgs",
|
||||
42,
|
||||
None,
|
||||
{"action": "edit", "path": "x.py", "performed": True, "tracked": True},
|
||||
]
|
||||
|
||||
def test_assess_final_report_validator_survives_malformed_entries(self):
|
||||
result = frv.assess_final_report_validator(
|
||||
_pr703_style_report(),
|
||||
"review_pr",
|
||||
action_log=self.MALFORMED,
|
||||
)
|
||||
self.assertIsInstance(result, dict)
|
||||
rule_ids = {f["rule_id"] for f in result["findings"]}
|
||||
self.assertIn("shared.action_log_malformed", rule_ids)
|
||||
|
||||
def test_malformed_entry_errors_are_sanitized(self):
|
||||
_entries, findings = frv.sanitize_action_log(["secret-token-abc123"])
|
||||
self.assertEqual(len(findings), 1)
|
||||
reason = findings[0]["reason"]
|
||||
self.assertNotIn("secret-token-abc123", reason)
|
||||
self.assertIn("str", reason)
|
||||
self.assertIn("entry 0", reason)
|
||||
|
||||
def test_non_list_action_log_is_reported_not_raised(self):
|
||||
entries, findings = frv.sanitize_action_log("not-a-list")
|
||||
self.assertEqual(entries, [])
|
||||
self.assertEqual(len(findings), 1)
|
||||
self.assertIn("not a list", findings[0]["reason"])
|
||||
|
||||
def test_performed_file_mutations_skips_non_dict_entries(self):
|
||||
performed = rp._performed_file_mutations(
|
||||
["oops", {"action": "edited", "path": "a.py"}]
|
||||
)
|
||||
self.assertEqual(len(performed), 1)
|
||||
self.assertEqual(performed[0]["path"], "a.py")
|
||||
|
||||
def test_schema_entrypoint_survives_string_only_log(self):
|
||||
result = assess_review_final_report_schema(
|
||||
_pr703_style_report(),
|
||||
action_log=["just a string", "another string"],
|
||||
)
|
||||
self.assertIsInstance(result, dict)
|
||||
|
||||
|
||||
class TestLegacyFieldRequirementsRemoved(unittest.TestCase):
|
||||
"""#698: prohibited legacy fields must not be REQUIRED of reports."""
|
||||
|
||||
PROHIBITED = (
|
||||
"Pinned reviewed head",
|
||||
"Scratch worktree used",
|
||||
"Worktree path",
|
||||
"Worktree dirty",
|
||||
"Workspace mutations",
|
||||
"Mutations",
|
||||
"Next",
|
||||
"Issue/PR",
|
||||
"Branch/SHA",
|
||||
"Files changed",
|
||||
)
|
||||
|
||||
def test_review_role_field_table_has_no_prohibited_requirements(self):
|
||||
names = [name for name, _ in rp.HANDOFF_ROLE_FIELDS["review"]]
|
||||
for prohibited in ("Pinned reviewed head", "Scratch worktree used",
|
||||
"Worktree path", "Worktree dirty"):
|
||||
self.assertNotIn(prohibited, names)
|
||||
|
||||
def test_merger_role_field_table_has_no_pinned_reviewed_head(self):
|
||||
names = [name for name, _ in rp.HANDOFF_ROLE_FIELDS["merger"]]
|
||||
self.assertNotIn("Pinned reviewed head", names)
|
||||
|
||||
def test_canonical_report_missing_fields_never_include_prohibited(self):
|
||||
result = rp.assess_controller_handoff(
|
||||
_pr703_style_report(), role="review"
|
||||
)
|
||||
for prohibited in self.PROHIBITED:
|
||||
self.assertNotIn(prohibited, result.get("missing_fields") or [])
|
||||
|
||||
def test_canonical_pr703_report_satisfies_required_fields(self):
|
||||
result = rp.assess_controller_handoff(
|
||||
_pr703_style_report(), role="review"
|
||||
)
|
||||
self.assertEqual(result.get("missing_fields") or [], [])
|
||||
self.assertEqual(result.get("verdict"), "complete")
|
||||
|
||||
|
||||
class TestBlockedReportAccepted(unittest.TestCase):
|
||||
"""#698: blocked run with no reviewed head / verdict is legitimate."""
|
||||
|
||||
def test_stale_head_proof_waived_before_validation(self):
|
||||
result = pwl.assess_reviewer_stale_head_final_report(
|
||||
_blocked_preflight_report()
|
||||
)
|
||||
self.assertTrue(result["proven"])
|
||||
self.assertEqual(result.get("phase"), "blocked_before_validation")
|
||||
|
||||
def test_blocked_report_passes_schema_validation(self):
|
||||
result = assess_review_final_report_schema(_blocked_preflight_report())
|
||||
blocking = [
|
||||
f for f in result["findings"] if f["severity"] == "block"
|
||||
]
|
||||
self.assertEqual(blocking, [], blocking)
|
||||
|
||||
def test_verdict_phase_still_demands_approval_head_proof(self):
|
||||
report = _blocked_preflight_report().replace(
|
||||
"- Review decision: none",
|
||||
"- Review decision: approve",
|
||||
).replace(
|
||||
"- Candidate head SHA: none",
|
||||
f"- Candidate head SHA: {REVIEWED_HEAD}",
|
||||
)
|
||||
result = pwl.assess_reviewer_stale_head_final_report(report)
|
||||
self.assertFalse(result["proven"])
|
||||
joined = " ".join(result["reasons"])
|
||||
self.assertIn("before approval", joined)
|
||||
|
||||
def test_merge_phase_still_demands_merge_head_proof(self):
|
||||
report = _pr703_style_report().replace(
|
||||
"- Merge result: none",
|
||||
"- Merge result: merged",
|
||||
)
|
||||
result = pwl.assess_reviewer_stale_head_final_report(report)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertIn(
|
||||
"final live head SHA before merge not stated",
|
||||
result["reasons"],
|
||||
)
|
||||
|
||||
def test_validation_phase_demands_push_disclosure(self):
|
||||
report = _pr703_style_report().replace(
|
||||
"- Pushes occurred during validation: no\n", ""
|
||||
)
|
||||
result = pwl.assess_reviewer_stale_head_final_report(report)
|
||||
self.assertFalse(result["proven"])
|
||||
self.assertIn(
|
||||
"whether push occurred during validation not stated",
|
||||
result["reasons"],
|
||||
)
|
||||
|
||||
|
||||
class TestLeaseReleaseVsPostMergeCleanup(unittest.TestCase):
|
||||
"""#698 (PR #703 review reproduction): lease release is not cleanup."""
|
||||
|
||||
def test_lease_release_cleanup_mutations_do_not_demand_checklist(self):
|
||||
result = pmcp.assess_post_merge_cleanup_proof(_pr703_style_report())
|
||||
self.assertFalse(result["block"], result["reasons"])
|
||||
|
||||
def test_release_tool_name_alone_is_recognized(self):
|
||||
report = _pr703_style_report(
|
||||
cleanup_mutations="gitea_release_reviewer_pr_lease comment 11244"
|
||||
)
|
||||
result = pmcp.assess_post_merge_cleanup_proof(report)
|
||||
self.assertFalse(result["block"], result["reasons"])
|
||||
|
||||
def test_substantive_non_lease_cleanup_still_demands_checklist(self):
|
||||
report = _pr703_style_report(
|
||||
cleanup_mutations="deleted stale scratch directory manually"
|
||||
)
|
||||
result = pmcp.assess_post_merge_cleanup_proof(report)
|
||||
self.assertTrue(result["block"])
|
||||
|
||||
def test_remote_branch_delete_claims_still_demand_full_proof(self):
|
||||
report = _pr703_style_report(
|
||||
cleanup_mutations="gitea_delete_branch removed the remote branch"
|
||||
)
|
||||
result = pmcp.assess_post_merge_cleanup_proof(report)
|
||||
self.assertTrue(result["block"])
|
||||
self.assertTrue(
|
||||
any("remote branch deletion missing" in r for r in result["reasons"])
|
||||
)
|
||||
|
||||
def test_full_schema_run_accepts_lease_release_report(self):
|
||||
result = assess_review_final_report_schema(_pr703_style_report())
|
||||
lease_cleanup_blocks = [
|
||||
f for f in result["findings"]
|
||||
if f["rule_id"] == "reviewer.post_merge_cleanup_proof"
|
||||
]
|
||||
self.assertEqual(lease_cleanup_blocks, [], lease_cleanup_blocks)
|
||||
|
||||
|
||||
class TestStructuredProofRecognition(unittest.TestCase):
|
||||
"""#698: structured workflow-load and validation proof must be accepted."""
|
||||
|
||||
def test_key_value_workflow_proof_recognized(self):
|
||||
findings = frv._rule_reviewer_workflow_load_boundary(
|
||||
_pr703_style_report()
|
||||
)
|
||||
self.assertEqual(findings, [], findings)
|
||||
|
||||
def test_colon_form_workflow_proof_still_recognized(self):
|
||||
report = _pr703_style_report().replace(
|
||||
"- Workflow-load helper result: workflow_hash=da045d1e1f1f "
|
||||
"boundary_status=clean",
|
||||
"- Workflow-load helper result: workflow_hash: da045d1e1f1f, "
|
||||
"boundary_status: clean",
|
||||
)
|
||||
findings = frv._rule_reviewer_workflow_load_boundary(report)
|
||||
self.assertEqual(findings, [], findings)
|
||||
|
||||
def test_incomplete_structured_proof_still_blocks(self):
|
||||
report = _pr703_style_report().replace(
|
||||
"workflow_hash=da045d1e1f1f boundary_status=clean",
|
||||
"workflow_hash=da045d1e1f1f",
|
||||
)
|
||||
findings = frv._rule_reviewer_workflow_load_boundary(report)
|
||||
self.assertTrue(findings)
|
||||
self.assertIn("boundary_status", findings[0]["reason"])
|
||||
|
||||
def test_validation_counts_accepted_as_pass_proof(self):
|
||||
# "Validation: focused 50 passed; ..." must satisfy the reviewed-head
|
||||
# validation-proof rule (PR #703 reproduction).
|
||||
result = assess_review_final_report_schema(_pr703_style_report())
|
||||
head_blocks = [
|
||||
f for f in result["findings"]
|
||||
if f["rule_id"] == "reviewer.reviewed_head_without_validation"
|
||||
]
|
||||
self.assertEqual(head_blocks, [], head_blocks)
|
||||
|
||||
|
||||
class TestAuthoritativeMutationInference(unittest.TestCase):
|
||||
"""#698: review mutations inferred only from authoritative evidence."""
|
||||
|
||||
def test_read_only_entries_do_not_imply_mutations(self):
|
||||
report = "## Controller Handoff\n- Mutations: none\n"
|
||||
findings = frv._rule_reviewer_vague_mutations_none(
|
||||
report,
|
||||
action_log=[
|
||||
{"action": "gitea_view_pr"},
|
||||
{"action": "gitea_get_pr_review_feedback", "performed": False},
|
||||
],
|
||||
)
|
||||
self.assertEqual(findings, [], findings)
|
||||
|
||||
def test_performed_mutation_still_blocks_vague_none(self):
|
||||
report = "## Controller Handoff\n- Mutations: none\n"
|
||||
findings = frv._rule_reviewer_vague_mutations_none(
|
||||
report,
|
||||
action_log=[{"action": "edit", "path": "a.py", "performed": True}],
|
||||
)
|
||||
self.assertTrue(findings)
|
||||
|
||||
def test_gated_rejection_is_not_a_mutation(self):
|
||||
report = "## Controller Handoff\n- Mutations: none\n"
|
||||
findings = frv._rule_reviewer_vague_mutations_none(
|
||||
report,
|
||||
action_log=[
|
||||
{"action": "edit", "path": "a.py", "performed": True,
|
||||
"gated_rejected": True},
|
||||
],
|
||||
)
|
||||
self.assertEqual(findings, [], findings)
|
||||
|
||||
|
||||
class TestValidatorRuleErrorContainment(unittest.TestCase):
|
||||
"""#698: a defective rule fails closed with a sanitized error."""
|
||||
|
||||
def test_rule_exception_becomes_sanitized_block_finding(self):
|
||||
def _boom(report_text):
|
||||
raise ValueError("raw secret detail that must not leak")
|
||||
|
||||
original = frv._RULES_BY_TASK["review_pr"]
|
||||
frv._RULES_BY_TASK["review_pr"] = [_boom]
|
||||
try:
|
||||
result = frv.assess_final_report_validator(
|
||||
"report body", "review_pr"
|
||||
)
|
||||
finally:
|
||||
frv._RULES_BY_TASK["review_pr"] = original
|
||||
self.assertTrue(result["blocked"])
|
||||
finding = next(
|
||||
f for f in result["findings"]
|
||||
if f["rule_id"] == "shared.validator_rule_error"
|
||||
)
|
||||
self.assertNotIn("raw secret detail", finding["reason"])
|
||||
self.assertIn("ValueError", finding["reason"])
|
||||
self.assertIn("_boom", finding["reason"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -957,17 +957,20 @@ class TestControllerHandoff(unittest.TestCase):
|
||||
if not line.startswith("- Workspace mutations:"))
|
||||
result = assess_controller_handoff(review_base, role="review")
|
||||
self.assertEqual(result["verdict"], "incomplete")
|
||||
self.assertIn("Pinned reviewed head", result["missing_fields"])
|
||||
self.assertIn("Worktree path", result["missing_fields"])
|
||||
# #698: the canonical schema forbids the legacy fields, so the
|
||||
# validator must demand the canonical names instead.
|
||||
self.assertIn("Reviewed head SHA", result["missing_fields"])
|
||||
self.assertIn("Review worktree path", result["missing_fields"])
|
||||
self.assertIn("Merge result", result["missing_fields"])
|
||||
for legacy in ("Pinned reviewed head", "Scratch worktree used"):
|
||||
self.assertNotIn(legacy, result["missing_fields"])
|
||||
|
||||
complete = review_base + "\n" + "\n".join([
|
||||
"- Selected PR: #999",
|
||||
"- Reviewer eligibility: passed",
|
||||
"- Pinned reviewed head: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9",
|
||||
"- Worktree path: /repo/branches/review-pr-999",
|
||||
"- Worktree dirty: no",
|
||||
"- Scratch worktree used: yes (/repo/branches/review-pr-999)",
|
||||
"- Reviewed head SHA: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9",
|
||||
"- Review worktree path: /repo/branches/review-pr-999",
|
||||
"- Review worktree dirty before validation: no",
|
||||
"- Unrelated local mutations: none",
|
||||
"- Review decision: approve",
|
||||
"- Merge result: merged",
|
||||
@@ -1125,10 +1128,9 @@ class TestReviewHandoffPreciseMutationCategories(unittest.TestCase):
|
||||
"- Safety: no self-review; no self-merge; no secrets",
|
||||
"- Selected PR: #999",
|
||||
"- Reviewer eligibility: passed",
|
||||
"- Pinned reviewed head: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9",
|
||||
"- Reviewed head SHA: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9",
|
||||
"- Worktree path: /repo/branches/review-pr-999",
|
||||
"- Worktree dirty: no",
|
||||
"- Scratch worktree used: yes (/repo/branches/review-pr-999)",
|
||||
"- Unrelated local mutations: none",
|
||||
"- Review decision: approve",
|
||||
"- Merge result: none",
|
||||
|
||||
Reference in New Issue
Block a user