Compare commits

..
Author SHA1 Message Date
sysadmin 054628c50d Merge pull request 'feat(continuation): formalize continuation proofs for claim and push (closes #189)' (#253) from feat/issue-189-continuation-mode-proofs into feat/issue-188-continuation-selection-wall 2026-07-06 11:27:25 -05:00
sysadmin 1158594a20 feat(continuation): formalize continuation proofs for claim and push (closes #189)
Extend #188 continuation mode with issue claim status evidence,
force-with-lease push reporting, canonical secret sweep helper, and
composite final-report checks wired into assess_issue_selection_final_report.
2026-07-06 11:54:40 -04:00
sysadmin e441b81d3b Merge branch 'master' into feat/issue-188-continuation-selection-wall 2026-07-06 11:44:59 -04:00
sysadminandClaude Opus 4.8 dc41b685d0 feat(author): add continuation-mode selection wall for open-PR issues (#188)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-06 11:40:35 -04:00
sysadminandClaude Opus 4.8 63a7ba8287 feat(author): add continuation-mode selection wall for open-PR issues (#188)
Issues already represented by open PRs must not be selected for fresh work
unless the operator explicitly requests continuation. Adds classification,
continuation report proofs (old/new head SHA, PR author, branch), contradictory
no-PR claim detection, and edited-PR inventory coverage checks.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-06 11:40:20 -04:00
sysadmin bab803ff3d Merge pull request 'test: require issue-lock proof in author handoffs (closes #208)' (#235) from feat/issue-208-issue-lock-proof into master 2026-07-06 10:39:12 -05:00
sysadminandClaude Opus 4.8 4bc02a8c7d fix(reviewer): restore #198 empty-queue helpers for PR #235 (#208)
Re-introduce assess_empty_queue_report and parse_trust_gate_status_from_report
dropped when #208 was reverted from master. Wire build_final_report grading,
inventory handoff trust-gate fields, and tests so capability_stop_terminal
imports resolve while issue-lock proof enforcement remains.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-06 11:37:07 -04:00
jcwalker3 ec879df4c2 test: require issue-lock proof in author handoffs (#208) 2026-07-06 10:19:46 -05:00
4 changed files with 905 additions and 355 deletions
+488 -232
View File
@@ -664,6 +664,8 @@ def build_final_report(checkout_proof, inventory, validation, contamination,
report_text, review_decision_lock
)
empty_queue_report = assess_empty_queue_report(report_text)
contamination_status = contamination.get("status", "unknown")
checkout_proven = bool(checkout_proof.get("proven"))
validation_claimable = bool(validation.get("claimable"))
@@ -787,6 +789,11 @@ def build_final_report(checkout_proof, inventory, validation, contamination,
"reviewer worktree safety proof missing or failed (#233)"
)
downgrade_reasons.extend(worktree.get("reasons", []))
if empty_queue_report.get("claimed") and not empty_queue_report.get("proven"):
downgrade_reasons.append(
"empty-queue report missing or failed trust-gate proof (#198)"
)
downgrade_reasons.extend(empty_queue_report.get("reasons", []))
merge_allowed = (
identity_eligible
@@ -846,6 +853,12 @@ def build_final_report(checkout_proof, inventory, validation, contamination,
"unrelated_mutations_avoided": bool(
worktree.get("unrelated_mutations_avoided")
),
"empty_queue_trust_gate_proven": (
empty_queue_report.get("proven")
if empty_queue_report.get("claimed")
else True
),
"empty_queue_trust_gate_status": empty_queue_report.get("status"),
}
@@ -1005,6 +1018,7 @@ HANDOFF_ROLE_FIELDS = {
),
"author": (
("Selected issue", ("selected issue",)),
("Issue lock proof", ("issue lock proof", "lock before diff")),
("Claim/comment status", ("claim/comment status", "claim status",
"claim")),
("PR number opened", ("pr number opened", "pr opened", "pr number")),
@@ -1015,23 +1029,40 @@ HANDOFF_ROLE_FIELDS = {
("Repositories checked", ("repositories checked", "repos checked")),
("Open PR counts", ("open pr counts", "open pr count",
"open prs per repo")),
("PR inventory trust gate", ("pr inventory trust gate",
"pr_inventory_trust_gate.status",
"trust gate status")),
("Trust gate reasons", ("trust gate reasons",
"pr_inventory_trust_gate.reason")),
("Trust gate corroborated", ("trust gate corroborated",
"pr_inventory_trust_gate.corroborated")),
("Inventory profile", ("inventory profile", "inventory mcp profile")),
("Selected PR or reason", ("selected pr", "none selected",
"reason none selected")),
("Inventory completeness", ("inventory complete", "inventory scoped",
"inventory completeness")),
),
"issue_filing": (
("Issue created or updated", (
"issue created or updated",
"issue created",
"issue updated",
"created issue",
"updated issue",
)),
("Related issues", ("related issues",)),
"continuation": (
("Continuation mode", ("continuation mode", "continuation")),
("Existing PR", ("existing pr", "pr number")),
("PR author", ("pr author", "existing pr author")),
("Issue claim status", ("issue claim", "claim status",
"status:in-progress")),
("Branch", ("branch", "existing branch")),
("Old PR head", ("old pr head", "old head")),
("New PR head", ("new pr head", "new head")),
("Session authored PR", ("session authored pr", "authored pr")),
("Why continuation allowed", ("why continuation", "continuation allowed")),
),
}
# Canonical secret/provenance sweep for comparable continuation runs (#189).
CANONICAL_SECRET_SWEEP_COMMAND = (
"git diff prgs/master...HEAD | rg -i "
"'(token|password|secret|api[_-]?key|authorization:)'"
)
CANONICAL_SECRET_SWEEP_SCOPE = "full feature-branch diff against prgs/master"
def _handoff_section_lines(report_text):
"""Return the lines of the Controller Handoff section, or None."""
@@ -1408,111 +1439,226 @@ def format_pr_inventory_trust_gate_report(gate: dict) -> list[str]:
return lines
_SHA_EVIDENCE_LABEL = re.compile(
r"(?:\b(?:old|new)\s+head\b|\bhead\s+sha\b|\bcommit\s+sha\b|\bsha\b)"
r"\s*[:=]?\s*([0-9a-f]+)",
_EMPTY_QUEUE_CLAIM = re.compile(
r"\b0 open pr|\bno open pr|\bno eligible pr|\bempty (?:review )?queue|"
r"nothing to review|queue cleared|inventory empty|"
r"open pr count:\s*0|workflow correctly stops with nothing",
re.I,
)
_WEAK_EMPTY_QUEUE_CORROBORATION = re.compile(
r"latest commit.*(?:merge|pr #)|merge of pr #|"
r"master latest commit|recent merge proves",
re.I,
)
_TRUST_GATE_STATUS_LINE = re.compile(
r"pr_inventory_trust_gate\.status:\s*(\S+)",
re.I,
)
def parse_trust_gate_status_from_report(report_text: str | None) -> str | None:
"""Extract ``pr_inventory_trust_gate.status`` from report text, if present."""
match = _TRUST_GATE_STATUS_LINE.search(report_text or "")
return match.group(1).strip().lower() if match else None
def assess_empty_queue_report(
report_text: str | None,
*,
trust_gate: dict | None = None,
task_role: str | None = None,
inventory_profile: str | None = None,
) -> dict:
"""Issue #198: empty-queue reports must cite the formal trust-gate result.
Blocks reports that claim an empty queue without
``pr_inventory_trust_gate.status == trusted_empty``, required inventory
metadata, or that rely on weak corroboration (e.g. a recent merge commit).
"""
text = report_text or ""
lower = text.lower()
reasons: list[str] = []
missing: list[str] = []
if not _EMPTY_QUEUE_CLAIM.search(text):
return {
"claimed": False,
"proven": True,
"block": False,
"status": None,
"missing_fields": [],
"reasons": [],
}
status = (
(trust_gate or {}).get("status")
or parse_trust_gate_status_from_report(text)
)
status_norm = (status or "").strip().lower() or None
if not status_norm:
missing.append("pr_inventory_trust_gate.status")
reasons.append(
"empty-queue report missing pr_inventory_trust_gate.status; "
"fail closed"
)
elif status_norm != "trusted_empty":
reasons.append(
f"empty-queue report has trust-gate status '{status_norm}', "
"not trusted_empty"
)
has_gate_reasons = (
"pr_inventory_trust_gate.reason" in lower
or bool((trust_gate or {}).get("reasons"))
)
if status_norm and status_norm != "trusted_empty" and not has_gate_reasons:
missing.append("pr_inventory_trust_gate.reasons")
if status_norm == "trusted_empty":
if "pr_inventory_trust_gate.corroborated" not in lower and (
trust_gate or {}
).get("corroborated") is not True:
missing.append("pr_inventory_trust_gate.corroborated")
inventory_markers = (
"repository:",
"remote:",
"owner:",
"state filter:",
"state_filter:",
)
if not any(marker in lower for marker in inventory_markers):
missing.append("inventory remote/owner/repo/state filter")
profile_markers = (
"mcp profile:",
"mcp-profile:",
"inventory profile:",
"active profile:",
)
has_profile = (
any(marker in lower for marker in profile_markers)
or bool((inventory_profile or "").strip())
)
if not has_profile:
missing.append("inventory MCP profile")
if _WEAK_EMPTY_QUEUE_CORROBORATION.search(text):
if "pr_inventory_trust_gate.status: trusted_empty" not in lower:
reasons.append(
"weak corroboration (recent merge commit) cannot substitute "
"for pr_inventory_trust_gate.status == trusted_empty"
)
role = (task_role or "").strip().lower()
if role == "author" and re.search(
r"reviewer queue|nothing to review|review backlog empty",
text,
re.I,
):
reasons.append(
"author-bound session presented reviewer queue inventory as a "
"reviewer decision"
)
if missing:
reasons.extend(
f"empty-queue report missing required field: {field}"
for field in missing
)
proven = not reasons and not missing
return {
"claimed": True,
"proven": proven,
"block": not proven,
"status": status_norm,
"missing_fields": missing,
"reasons": reasons,
}
# ── Issue selection / continuation mode (#188) ───────────────────────────────
ISSUE_SELECTION_UNCLAIMED_NO_PR = "unclaimed_no_pr"
ISSUE_SELECTION_REPRESENTED_BY_OPEN_PR = "represented_by_open_pr"
ISSUE_SELECTION_IN_PROGRESS = "in_progress"
ISSUE_SELECTION_CONTINUATION_EXPLICIT = "continuation_explicit"
ISSUE_SELECTION_EXCLUDED = "excluded"
_NO_OPEN_PR_CLAIM = re.compile(
r"no duplicate pr|no open pr|no pr open|no eligible pr|"
r"no existing pr|without an open pr",
re.IGNORECASE,
)
def assess_issue_filing_sha_evidence(report_text: str) -> dict:
"""Issue #191: commit evidence must use full 40-hex SHAs."""
text = report_text or ""
reasons = []
for match in _SHA_EVIDENCE_LABEL.finditer(text):
sha = match.group(1).strip().lower()
if sha and not _FULL_SHA.match(sha):
reasons.append(
f"abbreviated SHA in evidence ({len(sha)} hex chars); "
"full 40-character SHA required"
)
return {
"complete": not reasons,
"downgraded": bool(reasons),
"reasons": reasons,
}
def assess_issue_filing_issue_reference(
report_text: str,
def classify_issue_for_selection(
issue_number: int,
*,
issue_number: int | None = None,
issue_title: str | None = None,
action: str = "created",
labels: list[str] | None = None,
open_prs: list[dict] | None = None,
operator_continuation_requested: bool = False,
continuation_issue_numbers: list[int] | None = None,
excluded: bool = False,
) -> dict:
"""Issue #191: reports must cite exact issue number and title."""
text = (report_text or "").lower()
reasons = []
if issue_number is None:
reasons.append("issue number proof missing from assessment context")
"""Classify one issue for author queue selection (#188)."""
label_set = {str(l).lower() for l in (labels or [])}
prs = list(open_prs or [])
continuation_issues = set(continuation_issue_numbers or [])
if excluded:
status = ISSUE_SELECTION_EXCLUDED
selectable_for_fresh_work = False
reasons = ["issue explicitly excluded from selection"]
elif "status:in-progress" in label_set:
status = ISSUE_SELECTION_IN_PROGRESS
selectable_for_fresh_work = False
reasons = ["issue already marked status:in-progress"]
elif prs and (
operator_continuation_requested
or issue_number in continuation_issues
):
status = ISSUE_SELECTION_CONTINUATION_EXPLICIT
selectable_for_fresh_work = False
reasons = ["operator requested continuation for issue with open PR"]
elif prs:
status = ISSUE_SELECTION_REPRESENTED_BY_OPEN_PR
selectable_for_fresh_work = False
reasons = [
f"issue #{issue_number} already represented by open PR "
f"#{prs[0].get('number')}"
]
else:
num_patterns = (f"#{issue_number}", f"issue #{issue_number}",
f"issue {issue_number}")
if not any(p in text for p in num_patterns):
reasons.append(
f"report missing exact issue number #{issue_number}"
)
if issue_title:
title_fragment = issue_title.strip().lower()[:40]
if title_fragment and title_fragment not in text:
reasons.append("report missing exact issue title")
action = (action or "created").strip().lower()
if action == "created" and "creat" not in text:
reasons.append(
"report must distinguish new issue creation from update"
)
if action == "updated" and "updat" not in text:
reasons.append(
"report must distinguish issue update from new creation"
)
status = ISSUE_SELECTION_UNCLAIMED_NO_PR
selectable_for_fresh_work = True
reasons = []
return {
"complete": not reasons,
"downgraded": bool(reasons),
"issue_number": issue_number,
"status": status,
"selectable_for_fresh_work": selectable_for_fresh_work,
"open_prs": prs,
"reasons": reasons,
}
def assess_issue_filing_mutation_capability(
report_text: str,
mutations: list[str] | None,
resolved_capabilities: dict | None = None,
) -> dict:
"""Issue #191: every mutation needs exact capability proof in the report."""
import task_capability_map
text = (report_text or "").lower()
def assess_fresh_issue_selection(classifications: list[dict] | None) -> dict:
"""Fail closed when fresh selection picks an issue with an open PR."""
reasons = []
mutation_list = list(mutations or [])
if not mutation_list:
reasons.append("no mutations listed for capability proof")
cap_proof = assess_capability_proof(resolved_capabilities or {})
if not cap_proof.get("proven"):
reasons.extend(cap_proof.get("reasons") or [])
for task in mutation_list:
try:
permission = task_capability_map.required_permission(task)
except KeyError:
reasons.append(f"unknown mutation task '{task}'")
for item in classifications or []:
if item.get("selectable_for_fresh_work"):
continue
if permission.lower() not in text and task.replace("_", " ") not in text:
if item.get("status") == ISSUE_SELECTION_CONTINUATION_EXPLICIT:
continue
if item.get("status") == ISSUE_SELECTION_REPRESENTED_BY_OPEN_PR:
reasons.append(
f"mutation '{task}' missing exact capability proof "
f"('{permission}')"
f"issue #{item.get('issue_number')} has open PR and was "
"selected for fresh work without continuation mode"
)
if task == "set_issue_labels":
label_proof = (
"label change",
"labels changed",
"set label",
"label mutation",
"issue labels",
"label:",
)
if not any(p in text for p in label_proof):
reasons.append(
"label mutation missing label-specific capability proof"
)
return {
"complete": not reasons,
"downgraded": bool(reasons),
@@ -1520,120 +1666,67 @@ def assess_issue_filing_mutation_capability(
}
def assess_issue_filing_mutation_scope(
report_text: str,
*,
performed_mutations: list[str] | None = None,
forbidden_mutations: list[str] | None = None,
) -> dict:
"""Issue #191: single-mutation runs must state scope and absent mutations."""
text = (report_text or "").lower()
performed = list(performed_mutations or [])
forbidden = list(forbidden_mutations or [
"label", "comment", "pr", "review", "merge", "close",
])
reasons = []
if len(performed) == 1:
only_phrases = ("only mutation", "only mutations", "sole mutation")
if not any(p in text for p in only_phrases):
reasons.append(
"single-mutation run must state 'Only mutation(s): ...'"
)
for absent in forbidden:
if absent == "comment" and performed[0] in (
"comment_issue", "create_issue",
):
continue
if absent in text and f"no {absent}" not in text:
if any(
phrase in text
for phrase in (
f"no {absent}",
f"no {absent}s",
f"without {absent}",
"none performed",
"none.",
)
):
continue
if performed == ["create_issue"]:
for check in ("label", "comment", "pr", "review", "merge"):
if check in text and f"no {check}" not in text:
if not any(
n in text
for n in (
f"no {check}",
f"no {check}s",
"none performed",
"confirm no",
)
):
reasons.append(
f"create-only run must confirm no {check} "
"mutations were performed"
)
def canonical_secret_sweep_report(*, clean: bool) -> dict:
"""Return a sweep report using the canonical command/scope (#189)."""
return {
"complete": not reasons,
"downgraded": bool(reasons),
"reasons": reasons,
"method": CANONICAL_SECRET_SWEEP_COMMAND,
"scope": CANONICAL_SECRET_SWEEP_SCOPE,
"clean": clean,
}
def assess_issue_filing_duplicate_summary(
def assess_force_with_lease_push_report(
report_text: str,
*,
issues_searched: int | None = None,
closest_matches: list[dict] | None = None,
duplicate_result: dict | None = None,
old_remote_head: str | None = None,
expected_lease_head: str | None = None,
new_pushed_head: str | None = None,
branch_pushed: str | None = None,
used_force_with_lease: bool | None = None,
) -> dict:
"""Issue #191: duplicate-check evidence must be summarized in the report."""
text = (report_text or "").lower()
"""Issue #189: force-with-lease pushes must disclose lease evidence."""
if not used_force_with_lease:
return {"complete": True, "downgraded": False, "reasons": []}
text = report_text or ""
lower = text.lower()
reasons = []
dup = duplicate_result or {}
if issues_searched is not None:
count_tokens = (
str(issues_searched),
f"{issues_searched} open",
f"{issues_searched} issue",
)
if not any(t in text for t in count_tokens):
reasons.append(
"duplicate-check summary missing open-issues searched count"
)
matches = closest_matches if closest_matches is not None else dup.get("matches")
for match in matches or []:
num = match.get("number")
if num is not None and f"#{num}" not in text and str(num) not in text:
reasons.append(
f"duplicate-check summary missing closest issue #{num}"
)
break
justification_phrases = (
"why new",
"why update",
"rejected update",
"new issue justified",
"not a duplicate",
"no duplicate",
"justified",
"instead of expanding",
)
if not any(p in text for p in justification_phrases):
if "force-with-lease" not in lower and "force with lease" not in lower:
reasons.append(
"duplicate-check summary missing why update was rejected or "
"why a new issue was justified"
"continuation push used force-with-lease but report does not say so"
)
dup_proof = issue_duplicate_gate.assess_duplicate_search_proof(
report_text, matches or []
)
if not dup_proof.get("valid"):
reasons.extend(dup_proof.get("reasons") or [])
for label, sha in (
("old remote", old_remote_head),
("lease", expected_lease_head),
("pushed", new_pushed_head),
):
if not sha:
reasons.append(f"force-with-lease proof missing {label} head SHA")
elif not _FULL_SHA.match(sha.lower()):
reasons.append(
f"force-with-lease {label} head SHA is not a full 40-hex SHA"
)
elif sha.lower() not in lower:
reasons.append(
f"continuation report missing {label} head SHA in evidence"
)
if branch_pushed:
if branch_pushed.lower() not in lower:
reasons.append("continuation report missing pushed branch name")
only_branch_tokens = (
"only feature branch",
"push branch only",
"only the feature branch",
"only pushed feature branch",
)
if not any(t in lower for t in only_branch_tokens):
reasons.append(
"continuation report missing confirmation that only the "
"feature branch was pushed"
)
return {
"complete": not reasons,
@@ -1642,63 +1735,226 @@ def assess_issue_filing_duplicate_summary(
}
def assess_issue_filing_final_report(
def assess_continuation_mode_report(
report_text: str,
*,
pr_number: int | None = None,
pr_author: str | None = None,
issue_number: int | None = None,
issue_title: str | None = None,
action: str = "created",
mutations: list[str] | None = None,
resolved_capabilities: dict | None = None,
issues_searched: int | None = None,
closest_matches: list[dict] | None = None,
duplicate_result: dict | None = None,
performed_mutations: list[str] | None = None,
issue_claim_status: str | None = None,
branch: str | None = None,
old_head_sha: str | None = None,
new_head_sha: str | None = None,
session_authored_pr: bool | None = None,
continuation_allowed_reason: str | None = None,
) -> dict:
"""Issue #191: composite A-bar for issue-filing final reports."""
"""Issue #188/#189: continuation mode must disclose full PR evidence."""
text = report_text or ""
lower = text.lower()
reasons = []
if not any(p in lower for p in ("continuation", "continue pr", "continue issue")):
reasons.append("report does not declare continuation mode")
if pr_number is not None:
if f"#{pr_number}" not in lower and f"pr {pr_number}" not in lower:
reasons.append(f"continuation report missing PR #{pr_number}")
if pr_author and pr_author.lower() not in lower:
reasons.append("continuation report missing PR author")
if issue_number is not None:
if (
f"#{issue_number}" not in lower
and f"issue #{issue_number}" not in lower
and f"issue {issue_number}" not in lower
):
reasons.append(f"continuation report missing issue #{issue_number}")
if issue_claim_status:
claim_lower = issue_claim_status.lower()
claim_tokens = (
claim_lower,
"claim status",
"issue claim",
"status:in-progress",
)
if not any(t in lower for t in claim_tokens):
reasons.append("continuation report missing issue claim status")
if branch and branch.lower() not in lower:
reasons.append("continuation report missing branch name")
for label, sha in (("old", old_head_sha), ("new", new_head_sha)):
if not sha:
reasons.append(f"continuation proof missing {label} head SHA")
elif not _FULL_SHA.match(sha.lower()):
reasons.append(
f"continuation {label} head SHA is not a full 40-hex SHA"
)
elif sha.lower() not in lower:
reasons.append(
f"continuation report missing {label} head SHA in evidence"
)
if session_authored_pr is not None:
authored_tokens = ("session authored", "authored pr", "own pr", "my pr")
if not any(t in lower for t in authored_tokens):
reasons.append(
"continuation report missing whether session authored the PR"
)
if continuation_allowed_reason:
reason_lower = continuation_allowed_reason.lower()
if (
reason_lower not in lower
and not any(w in lower for w in reason_lower.split()[:3])
):
reasons.append("continuation report missing why continuation is allowed")
return {
"complete": not reasons,
"downgraded": bool(reasons),
"reasons": reasons,
}
def assess_contradictory_no_pr_claim(
report_text: str,
*,
edited_pr_numbers: list[int] | None = None,
issue_open_pr_map: dict[int, int] | None = None,
) -> dict:
"""Downgrade when report claims no open PR but later edits one."""
text = report_text or ""
lower = text.lower()
reasons = []
if not _NO_OPEN_PR_CLAIM.search(lower):
return {"complete": True, "downgraded": False, "reasons": []}
edited = list(edited_pr_numbers or [])
for pr_num in edited:
if f"#{pr_num}" in lower or f"pr {pr_num}" in lower:
reasons.append(
f"report claims no open PR but edited PR #{pr_num}"
)
for issue_num, pr_num in (issue_open_pr_map or {}).items():
if f"#{issue_num}" in lower or f"issue #{issue_num}" in lower:
if f"#{pr_num}" in lower or f"pr {pr_num}" in lower:
reasons.append(
f"report claims no open PR for issue #{issue_num} but "
f"PR #{pr_num} exists and was referenced"
)
return {
"complete": not reasons,
"downgraded": bool(reasons),
"reasons": reasons,
}
def assess_edited_pr_inventory_coverage(
report_text: str,
*,
edited_pr_numbers: list[int] | None = None,
inventoried_pr_numbers: list[int] | None = None,
) -> dict:
"""Open PR inventory must include PRs the run later edits (#188)."""
text = report_text or ""
lower = text.lower()
reasons = []
inventoried = set(inventoried_pr_numbers or [])
for pr_num in edited_pr_numbers or []:
if pr_num in inventoried:
continue
if f"#{pr_num}" not in lower and f"pr {pr_num}" not in lower:
reasons.append(
f"edited PR #{pr_num} missing from open PR inventory"
)
return {
"complete": not reasons,
"downgraded": bool(reasons),
"reasons": reasons,
}
def assess_issue_selection_final_report(
report_text: str,
*,
mode: str = "fresh",
classifications: list[dict] | None = None,
continuation_proof: dict | None = None,
edited_pr_numbers: list[int] | None = None,
inventoried_pr_numbers: list[int] | None = None,
issue_open_pr_map: dict[int, int] | None = None,
) -> dict:
"""Issue #188: composite A-bar for author issue-selection runs."""
handoff_role = "continuation" if mode == "continuation" else "author"
checks = {
"controller_handoff": assess_controller_handoff(
report_text, role="issue_filing"
report_text, role=handoff_role
),
"issue_reference": assess_issue_filing_issue_reference(
"contradictory_no_pr": assess_contradictory_no_pr_claim(
report_text,
issue_number=issue_number,
issue_title=issue_title,
action=action,
edited_pr_numbers=edited_pr_numbers,
issue_open_pr_map=issue_open_pr_map,
),
"sha_evidence": assess_issue_filing_sha_evidence(report_text),
"mutation_capability": assess_issue_filing_mutation_capability(
"edited_pr_inventory": assess_edited_pr_inventory_coverage(
report_text,
mutations or performed_mutations,
resolved_capabilities,
),
"mutation_scope": assess_issue_filing_mutation_scope(
report_text,
performed_mutations=performed_mutations or mutations,
),
"duplicate_summary": assess_issue_filing_duplicate_summary(
report_text,
issues_searched=issues_searched,
closest_matches=closest_matches,
duplicate_result=duplicate_result,
edited_pr_numbers=edited_pr_numbers,
inventoried_pr_numbers=inventoried_pr_numbers,
),
}
if mode == "fresh":
checks["fresh_selection"] = assess_fresh_issue_selection(classifications)
else:
proof = continuation_proof or {}
checks["continuation_mode"] = assess_continuation_mode_report(
report_text,
pr_number=proof.get("pr_number"),
pr_author=proof.get("pr_author"),
issue_number=proof.get("issue_number"),
issue_claim_status=proof.get("issue_claim_status"),
branch=proof.get("branch"),
old_head_sha=proof.get("old_head_sha"),
new_head_sha=proof.get("new_head_sha"),
session_authored_pr=proof.get("session_authored_pr"),
continuation_allowed_reason=proof.get("continuation_allowed_reason"),
)
push_proof = proof.get("push_proof") or {}
checks["force_with_lease_push"] = assess_force_with_lease_push_report(
report_text,
old_remote_head=push_proof.get("old_remote_head"),
expected_lease_head=push_proof.get("expected_lease_head"),
new_pushed_head=push_proof.get("new_pushed_head"),
branch_pushed=push_proof.get("branch_pushed"),
used_force_with_lease=push_proof.get("used_force_with_lease"),
)
sweep = proof.get("secret_sweep")
if sweep is not None:
checks["secret_sweep"] = assess_secret_sweep(sweep)
reasons = []
downgraded = False
for name, result in checks.items():
if result.get("verdict") == "missing":
verdict = result.get("verdict")
if verdict in ("missing", "incomplete"):
downgraded = True
reasons.extend(result.get("reasons") or [])
elif result.get("proven") is False:
downgraded = True
reasons.extend(
f"{name}: {r}" for r in (result.get("reasons") or [])
)
elif result.get("downgraded") or not result.get("complete", True):
downgraded = True
reasons.extend(
f"{name}: {r}" for r in (result.get("reasons") or [])
)
grade = "A" if not downgraded else "downgraded"
return {
"grade": grade,
"grade": "A" if not downgraded else "downgraded",
"downgraded": downgraded,
"checks": checks,
"reasons": reasons,
+11 -6
View File
@@ -377,14 +377,19 @@ Role-specific fields (append to the compact block):
- review/merge tasks: `Selected PR:`, `Reviewer eligibility:`,
`Pinned reviewed head:`, `Review decision:`, `Merge result:`,
`Linked issue status:`, `Cleanup status:`
- issue-filing tasks (#191): `Issue created or updated:`, `Related issues:`;
body must cite exact issue number/title, duplicate-search summary (issues
searched, closest matches, why update rejected / new issue justified), full
40-char SHAs when citing commits, exact mutation capability per change, and
`Only mutation(s):` when a single mutation was performed
(`review_proofs.assess_issue_filing_final_report`).
- author tasks: `Selected issue:`, `Claim/comment status:`,
`PR number opened:`, `No review/merge:` (explicit confirmation)
- continuation tasks (#188/#189): `Continuation mode:`, `Existing PR:`,
`PR author:`, `Issue claim status:`, `Branch:`, `Old PR head:`,
`New PR head:`, `Session authored PR:`, `Why continuation allowed:`
when rebasing with `git push --force-with-lease`, also record
`Old remote head:`, `Lease head:`, `Pushed head:`, and confirm
`Push branch only:` (feature branch only). Use the canonical secret sweep
from `review_proofs.CANONICAL_SECRET_SWEEP_COMMAND` so runs are
comparable. Issues with open PRs are excluded from fresh selection unless
the operator explicitly requests continuation
(`review_proofs.classify_issue_for_selection`,
`assess_issue_selection_final_report`)
- queue/inventory tasks: `Repositories checked:`, `Open PR counts:`,
`Selected PR or reason none selected:`, `Inventory completeness:`
+49 -13
View File
@@ -65,6 +65,20 @@ CREATE_PR_ENV = {
),
}
ISSUE_LOCK_FILE = "/tmp/gitea_issue_lock.json"
def _sample_issue_lock(issue_number=123, branch_name="feat/x", **overrides):
record = {
"issue_number": issue_number,
"branch_name": branch_name,
"remote": "dadeschools",
"org": "Scaled-Tech-Consulting",
"repo": "Gitea-Tools",
}
record.update(overrides)
return record
# ---------------------------------------------------------------------------
# Create Issue
@@ -127,15 +141,19 @@ class TestCreatePR(unittest.TestCase):
@patch("os.path.exists", return_value=True)
@patch("builtins.open")
def test_creates_pr(self, mock_open, mock_exists, _auth, mock_api, _role):
mock_open.return_value.__enter__.return_value.read.return_value = '{"issue_number": 123, "branch_name": "feat/x"}'
lock_json = json.dumps(_sample_issue_lock(issue_number=123, branch_name="feat/x"))
mock_open.return_value.__enter__.return_value.read.return_value = lock_json
mock_api.return_value = {"number": 3, "html_url": "https://example.com/pulls/3"}
with patch.dict(os.environ, CREATE_PR_ENV, clear=True):
result = gitea_create_pr(title="feat: X Closes #123", head="feat/x", base="main")
self.assertEqual(result["number"], 3)
self.assertNotIn("url", result)
mock_exists.assert_called_with(ISSUE_LOCK_FILE)
mock_open.assert_called_with(ISSUE_LOCK_FILE, "r", encoding="utf-8")
payload = mock_api.call_args[0][3]
self.assertEqual(payload["head"], "feat/x")
self.assertEqual(payload["base"], "main")
self.assertIn("Closes #123", payload["title"])
@patch("mcp_server.role_session_router.check_author_mutation_after_reviewer_stop",
return_value=(True, []))
@@ -144,13 +162,28 @@ class TestCreatePR(unittest.TestCase):
@patch("os.path.exists", return_value=True)
@patch("builtins.open")
def test_create_pr_reveal_opt_in_includes_url(self, mock_open, mock_exists, _auth, mock_api, _role):
mock_open.return_value.__enter__.return_value.read.return_value = '{"issue_number": 123, "branch_name": "feat/x"}'
lock_json = json.dumps(_sample_issue_lock(issue_number=123, branch_name="feat/x"))
mock_open.return_value.__enter__.return_value.read.return_value = lock_json
mock_api.return_value = {"number": 3, "html_url": "https://example.com/pulls/3"}
env = {**CREATE_PR_ENV, "GITEA_MCP_REVEAL_ENDPOINTS": "1"}
with patch.dict(os.environ, env, clear=True):
result = gitea_create_pr(title="feat: X Closes #123", head="feat/x", base="main")
self.assertIn("pulls/3", result["url"])
@patch("mcp_server.role_session_router.check_author_mutation_after_reviewer_stop",
return_value=(True, []))
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
@patch("os.path.exists", return_value=True)
@patch("builtins.open")
def test_create_pr_locked_issue_mismatch_fails(self, mock_open, mock_exists, _auth, _role):
lock_json = json.dumps(_sample_issue_lock(issue_number=123, branch_name="feat/x"))
mock_open.return_value.__enter__.return_value.read.return_value = lock_json
with patch.dict(os.environ, CREATE_PR_ENV, clear=True):
with self.assertRaises(ValueError) as ctx:
gitea_create_pr(title="feat: X Closes #999", head="feat/x", base="main")
self.assertIn("Closes #123", str(ctx.exception))
mock_open.assert_called_with(ISSUE_LOCK_FILE, "r", encoding="utf-8")
# ---------------------------------------------------------------------------
# Close Issue
@@ -2770,8 +2803,8 @@ class TestIssueLocking(unittest.TestCase):
"""Test issue locking and PR gating constraints."""
def tearDown(self):
if os.path.exists("/tmp/gitea_issue_lock.json"):
os.remove("/tmp/gitea_issue_lock.json")
if os.path.exists(ISSUE_LOCK_FILE):
os.remove(ISSUE_LOCK_FILE)
@patch("mcp_server.api_get_all")
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
@@ -2779,7 +2812,7 @@ class TestIssueLocking(unittest.TestCase):
mock_api.return_value = [] # no open PRs
res = gitea_lock_issue(issue_number=196, branch_name="feat/issue-196-mutations", remote="prgs")
self.assertTrue(res["success"])
self.assertTrue(os.path.exists("/tmp/gitea_issue_lock.json"))
self.assertTrue(os.path.exists(ISSUE_LOCK_FILE))
def test_lock_issue_mismatch_branch_fails(self):
with self.assertRaises(ValueError) as ctx:
@@ -2816,8 +2849,8 @@ class TestIssueLocking(unittest.TestCase):
return_value=(True, []))
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_create_pr_missing_lock_fails(self, _auth, _role):
if os.path.exists("/tmp/gitea_issue_lock.json"):
os.remove("/tmp/gitea_issue_lock.json")
if os.path.exists(ISSUE_LOCK_FILE):
os.remove(ISSUE_LOCK_FILE)
with patch.dict(os.environ, CREATE_PR_ENV, clear=True):
with self.assertRaises(RuntimeError) as ctx:
gitea_create_pr(title="feat: X Closes #196", head="feat/issue-196-mutations", remote="prgs")
@@ -2827,8 +2860,9 @@ class TestIssueLocking(unittest.TestCase):
return_value=(True, []))
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_create_pr_branch_mismatch_fails(self, _auth, _role):
with open("/tmp/gitea_issue_lock.json", "w", encoding="utf-8") as f:
json.dump({"issue_number": 196, "branch_name": "feat/issue-196-mutations"}, f)
with open(ISSUE_LOCK_FILE, "w", encoding="utf-8") as f:
json.dump(_sample_issue_lock(
issue_number=196, branch_name="feat/issue-196-mutations"), f)
with patch.dict(os.environ, CREATE_PR_ENV, clear=True):
with self.assertRaises(ValueError) as ctx:
gitea_create_pr(title="feat: X Closes #196", head="feat/issue-196-different", remote="prgs")
@@ -2838,8 +2872,9 @@ class TestIssueLocking(unittest.TestCase):
return_value=(True, []))
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_create_pr_forbidden_terms_fails(self, _auth, _role):
with open("/tmp/gitea_issue_lock.json", "w", encoding="utf-8") as f:
json.dump({"issue_number": 196, "branch_name": "feat/issue-196-mutations"}, f)
with open(ISSUE_LOCK_FILE, "w", encoding="utf-8") as f:
json.dump(_sample_issue_lock(
issue_number=196, branch_name="feat/issue-196-mutations"), f)
with patch.dict(os.environ, CREATE_PR_ENV, clear=True):
for term in ("equivalent to #196", "related to #196", "same as #196"):
with self.assertRaises(ValueError) as ctx:
@@ -2850,8 +2885,9 @@ class TestIssueLocking(unittest.TestCase):
return_value=(True, []))
@patch("mcp_server.get_auth_header", return_value=FAKE_AUTH)
def test_create_pr_missing_closes_ref_fails(self, _auth, _role):
with open("/tmp/gitea_issue_lock.json", "w", encoding="utf-8") as f:
json.dump({"issue_number": 196, "branch_name": "feat/issue-196-mutations"}, f)
with open(ISSUE_LOCK_FILE, "w", encoding="utf-8") as f:
json.dump(_sample_issue_lock(
issue_number=196, branch_name="feat/issue-196-mutations"), f)
with patch.dict(os.environ, CREATE_PR_ENV, clear=True):
with self.assertRaises(ValueError) as ctx:
gitea_create_pr(title="feat: X refs #196", head="feat/issue-196-mutations", remote="prgs")
+357 -104
View File
@@ -21,14 +21,22 @@ import unittest
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent))
from review_proofs import ( # noqa: E402
ISSUE_SELECTION_CONTINUATION_EXPLICIT,
ISSUE_SELECTION_REPRESENTED_BY_OPEN_PR,
assess_author_pr_report,
assess_capability_evidence,
assess_capability_proof,
assess_contradictory_no_pr_claim,
assess_continuation_mode_report,
assess_force_with_lease_push_report,
canonical_secret_sweep_report,
CANONICAL_SECRET_SWEEP_COMMAND,
assess_controller_handoff,
assess_edited_pr_inventory_coverage,
assess_empty_queue_report,
assess_fresh_issue_selection,
assess_inventory_completeness,
assess_issue_filing_final_report,
assess_issue_filing_mutation_capability,
assess_issue_filing_sha_evidence,
assess_issue_selection_final_report,
assess_reviewer_queue_inventory,
assess_live_state_recheck,
assess_review_mutation_final_report,
@@ -38,6 +46,7 @@ from review_proofs import ( # noqa: E402
assess_sweep_evidence,
assess_validation_report,
build_final_report,
classify_issue_for_selection,
pr_inventory_trust_gate,
resolve_repos_from_user_reference,
verify_pinned_head_checkout,
@@ -947,24 +956,46 @@ class TestControllerHandoff(unittest.TestCase):
result = assess_controller_handoff(complete, role="review")
self.assertEqual(result["verdict"], "complete")
def test_author_role_requires_author_fields(self):
complete = self.BASE_HANDOFF + "\n" + "\n".join([
"- Selected issue: #182",
def _author_role_fields(self, issue_number=182, pr_number=999):
return [
f"- Selected issue: #{issue_number}",
"- Issue lock proof: lock before diff on feat/x @ master",
"- Claim/comment status: comment-claimed",
"- PR number opened: #999",
f"- PR number opened: #{pr_number}",
"- No review/merge: confirmed",
])
]
def test_handoff_role_fields_author_includes_issue_lock_proof(self):
from review_proofs import HANDOFF_ROLE_FIELDS
names = [name for name, _ in HANDOFF_ROLE_FIELDS["author"]]
self.assertIn("Issue lock proof", names)
def test_author_role_requires_author_fields(self):
complete = self.BASE_HANDOFF + "\n" + "\n".join(self._author_role_fields())
result = assess_controller_handoff(complete, role="author")
self.assertEqual(result["verdict"], "complete")
result = assess_controller_handoff(self.BASE_HANDOFF, role="author")
self.assertEqual(result["verdict"], "incomplete")
self.assertIn("Issue lock proof", result["missing_fields"])
self.assertIn("No review/merge confirmation", result["missing_fields"])
def test_author_role_requires_issue_lock_proof(self):
without_lock = self.BASE_HANDOFF + "\n" + "\n".join([
"- Selected issue: #182",
"- Claim/comment status: comment-claimed",
"- PR number opened: #999",
"- No review/merge: confirmed",
])
result = assess_controller_handoff(without_lock, role="author")
self.assertEqual(result["verdict"], "incomplete")
self.assertIn("Issue lock proof", result["missing_fields"])
def test_author_role_rejects_equivalent_or_multiple_issues(self):
# 1. equivalent reference blocked
incomplete_eq = self.BASE_HANDOFF + "\n" + "\n".join([
"- Selected issue: Issue #194 / #196 equivalent",
"- Issue lock proof: lock before diff on feat/x @ master",
"- Claim/comment status: comment-claimed",
"- PR number opened: #999",
"- No review/merge: confirmed",
@@ -976,6 +1007,7 @@ class TestControllerHandoff(unittest.TestCase):
# 2. multiple issues blocked
incomplete_multi = self.BASE_HANDOFF + "\n" + "\n".join([
"- Selected issue: #194, #196",
"- Issue lock proof: lock before diff on feat/x @ master",
"- Claim/comment status: comment-claimed",
"- PR number opened: #999",
"- No review/merge: confirmed",
@@ -987,6 +1019,7 @@ class TestControllerHandoff(unittest.TestCase):
def test_author_role_rejects_fuzzy_pr_number(self):
incomplete_pr = self.BASE_HANDOFF + "\n" + "\n".join([
"- Selected issue: #196",
"- Issue lock proof: lock before diff on feat/issue-196 @ master",
"- Claim/comment status: comment-claimed",
"- PR number opened: PR #203 / #204 equivalent",
"- No review/merge: confirmed",
@@ -999,6 +1032,10 @@ class TestControllerHandoff(unittest.TestCase):
complete = self.BASE_HANDOFF + "\n" + "\n".join([
"- Repositories checked: Gitea-Tools, mcp-control-plane",
"- Open PR counts: 2 / 0",
"- PR inventory trust gate: trusted_nonempty / trusted_empty",
"- Trust gate reasons: none",
"- Trust gate corroborated: true",
"- Inventory profile: prgs-reviewer",
"- Selected PR or reason: none eligible (self-authored)",
"- Inventory completeness: complete, no pagination needed",
])
@@ -1018,23 +1055,17 @@ class TestControllerHandoff(unittest.TestCase):
def test_handoff_rejects_none_workspace_mutations_when_local_edits_exist(self):
# 1. Workspace mutations: none is rejected when local_edits is True
incomplete_eq = self.BASE_HANDOFF + "\n" + "\n".join([
"- Selected issue: #196",
"- Claim/comment status: comment-claimed",
"- PR number opened: #203",
"- No review/merge: confirmed",
])
incomplete_eq = self.BASE_HANDOFF + "\n" + "\n".join(
self._author_role_fields(issue_number=196, pr_number=203))
res = assess_controller_handoff(incomplete_eq, role="author", local_edits=True)
self.assertEqual(res["verdict"], "incomplete")
self.assertIn("Workspace mutations", res["missing_fields"])
# 2. Workspace mutations: edited files is allowed when local_edits is True
complete_eq = self.BASE_HANDOFF.replace("- Workspace mutations: none", "- Workspace mutations: edited review_proofs.py") + "\n" + "\n".join([
"- Selected issue: #196",
"- Claim/comment status: comment-claimed",
"- PR number opened: #203",
"- No review/merge: confirmed",
])
complete_eq = self.BASE_HANDOFF.replace(
"- Workspace mutations: none",
"- Workspace mutations: edited review_proofs.py",
) + "\n" + "\n".join(self._author_role_fields(issue_number=196, pr_number=203))
res2 = assess_controller_handoff(complete_eq, role="author", local_edits=True)
self.assertEqual(res2["verdict"], "complete")
@@ -1265,6 +1296,89 @@ class TestAssessReviewerQueueInventory(unittest.TestCase):
self.assertEqual(result["trust_gates"], {})
class TestAssessEmptyQueueReport(unittest.TestCase):
"""Issue #198: empty-queue reports require formal trust-gate proof."""
def _trusted_report(self, **extra):
lines = [
"Queue inventory complete.",
"Repository: Scaled-Tech-Consulting/Gitea-Tools",
"Open PR count: 0",
"pr_inventory_trust_gate.status: trusted_empty",
"pr_inventory_trust_gate.corroborated: true",
"Inventory profile: prgs-reviewer",
"Workflow correctly stops with nothing to review.",
]
lines.extend(extra)
return "\n".join(lines)
def test_non_empty_report_not_claimed(self):
result = assess_empty_queue_report("Reviewed PR #236 and merged.")
self.assertFalse(result["claimed"])
self.assertTrue(result["proven"])
def test_empty_claim_without_trust_gate_blocked(self):
report = (
"Open PR count: 0\n"
"Pagination complete: yes\n"
"Queue cleared."
)
result = assess_empty_queue_report(report)
self.assertTrue(result["claimed"])
self.assertFalse(result["proven"])
self.assertTrue(result["block"])
def test_trusted_empty_report_with_required_fields_passes(self):
result = assess_empty_queue_report(self._trusted_report())
self.assertTrue(result["proven"])
def test_weak_merge_commit_corroboration_blocked(self):
report = (
"Open PR count: 0\n"
"Master latest commit is merge of PR #79 so queue is empty."
)
result = assess_empty_queue_report(report)
self.assertFalse(result["proven"])
self.assertTrue(
any("weak corroboration" in r for r in result["reasons"])
)
def test_author_session_reviewer_queue_wording_blocked(self):
result = assess_empty_queue_report(
self._trusted_report(),
task_role="author",
)
self.assertFalse(result["proven"])
def test_build_final_report_downgrades_weak_empty_queue(self):
final = build_final_report(
checkout_proof=_good_checkout(),
inventory=_good_inventory(),
validation=_good_validation(),
contamination=_good_contamination(),
identity_eligible=True,
merge_performed=False,
issue_status_verified=True,
capability_evidence=_good_capability_evidence(),
sweep=_good_sweep(),
live_state=_good_live_state(),
role_boundary=_good_role_boundary(),
review_mutation=_good_review_mutation(),
controller_handoff=_good_handoff(),
capability_proof=_good_capability_proof(),
sweep_proof=_good_secret_sweep(),
worktree_proof={
"worktree_path": "/repo/branches/review-pr-1",
"porcelain_status": "",
"scratch_used": True,
"scratch_path": "/repo/branches/review-pr-1",
},
report_text="Open PR count: 0. Queue cleared.",
)
self.assertNotEqual(final["grade"], "A")
self.assertFalse(final["empty_queue_trust_gate_proven"])
class TestCapabilityEvidence(unittest.TestCase):
"""#179 gap 1: capability claims need exact evidence."""
@@ -1559,114 +1673,253 @@ class TestAuthorReporting(unittest.TestCase):
self.assertFalse(result["complete"])
class TestIssueFilingFinalReport(unittest.TestCase):
"""Issue #191: issue-filing runs need A-bar final report proofs."""
class TestIssueSelectionContinuation(unittest.TestCase):
"""Issue #188: continuation mode wall for issues with open PRs."""
ISSUE_TITLE = (
"Implement fail-closed continuation mode for issues already "
"represented by open PRs"
)
FULL_SHA = "a4060c5de00f2b1c9e88f4f6f0f3f9a7b2c1d0e9"
CAPABILITIES = {
"create_issue": {
"requested_task": "create_issue",
"required_operation_permission": "gitea.issue.create",
"allowed_in_current_session": True,
},
}
CLOSEST = [{"number": 183, "title": "Harden author-run reporting", "state": "open"}]
OLD_SHA = PINNED
NEW_SHA = OTHER
OPEN_PR = [{"number": 187, "head": {"ref": "feat/issue-183-harden-author-run-reporting"}}]
def _good_report(self, *, sha_line=None):
lines = [
"Created Gitea-Tools #189 — "
f"`{self.ISSUE_TITLE}`",
"Duplicate check: searched 12 open issues.",
"Closest existing: #183 — Harden author-run reporting "
"(update rejected — different scope).",
"Why new issue justified: continuation mode is distinct from #183.",
"Only mutation: issue creation (gitea.issue.create).",
"Confirm no labels, comments, PRs, reviews, merges, or closes.",
def test_open_pr_issue_excluded_from_fresh_selection(self):
classified = classify_issue_for_selection(
183, open_prs=self.OPEN_PR,
)
self.assertEqual(classified["status"], ISSUE_SELECTION_REPRESENTED_BY_OPEN_PR)
self.assertFalse(classified["selectable_for_fresh_work"])
blocked = assess_fresh_issue_selection([classified])
self.assertTrue(blocked["downgraded"])
def test_explicit_continuation_allows_represented_issue(self):
classified = classify_issue_for_selection(
183,
open_prs=self.OPEN_PR,
operator_continuation_requested=True,
)
self.assertEqual(classified["status"], ISSUE_SELECTION_CONTINUATION_EXPLICIT)
blocked = assess_fresh_issue_selection([classified])
self.assertFalse(blocked["downgraded"])
def test_contradictory_no_pr_claim_downgrades(self):
report = (
"Selected issue #183; no duplicate PR open. "
"Updated PR #187 on branch feat/issue-183-harden-author-run-reporting."
)
result = assess_contradictory_no_pr_claim(
report, edited_pr_numbers=[187], issue_open_pr_map={183: 187},
)
self.assertTrue(result["downgraded"])
def test_edited_pr_must_appear_in_inventory(self):
report = "Open PR inventory: PR #195 only."
result = assess_edited_pr_inventory_coverage(
report,
edited_pr_numbers=[187],
inventoried_pr_numbers=[195],
)
self.assertTrue(result["downgraded"])
def test_continuation_report_requires_old_and_new_head(self):
report = (
"Issue #182 continuation mode. PR #186. "
f"old head {self.OLD_SHA} -> new head {self.NEW_SHA}. "
"PR author: jcwalker3. Branch: feat/issue-182-controller-handoff. "
"Session authored PR: yes. Continuation allowed: operator requested."
)
result = assess_continuation_mode_report(
report,
pr_number=186,
pr_author="jcwalker3",
branch="feat/issue-182-controller-handoff",
old_head_sha=self.OLD_SHA,
new_head_sha=self.NEW_SHA,
session_authored_pr=True,
continuation_allowed_reason="operator requested continuation",
)
self.assertTrue(result["complete"])
def test_issue_selection_final_report_continuation_earns_a(self):
report = "\n".join([
"Issue #182 continuation mode — no new issue claimed.",
f"PR #186 updated: old head {self.OLD_SHA}, "
f"new head {self.NEW_SHA}.",
"PR author: jcwalker3. Branch: feat/issue-182-controller-handoff.",
"Session authored PR: yes.",
"Continuation allowed: operator requested rebase.",
"Open PR inventory included PR #186.",
"## Controller Handoff",
"- Task: file issue",
"- Task: continuation",
"- Repo: Scaled-Tech-Consulting/Gitea-Tools",
"- Role: author",
"- Identity: prgs-author",
"- Issue/PR: #189",
"- Branch/SHA: n/a",
"- Files changed: none",
"- Validation: duplicate gate + create_issue capability resolved",
"- Mutations: create_issue via gitea.issue.create",
"- Issue/PR: #182 / PR #186",
"- Branch/SHA: feat/issue-182-controller-handoff",
"- Files changed: review_proofs.py",
"- Validation: tests passed",
"- Mutations: push_branch",
"- Workspace mutations: none",
"- Current status: issue created",
"- Current status: PR mergeable",
"- Blockers: none",
"- Next: implementation",
"- Next: review",
"- Safety: no review/merge",
"- Issue created or updated: created #189",
"- Related issues: #183, #188",
]
if sha_line:
lines.insert(4, sha_line)
return "\n".join(lines)
def test_complete_issue_filing_report_earns_a(self):
result = assess_issue_filing_final_report(
self._good_report(),
issue_number=189,
issue_title=self.ISSUE_TITLE,
action="created",
mutations=["create_issue"],
resolved_capabilities=self.CAPABILITIES,
issues_searched=12,
closest_matches=self.CLOSEST,
performed_mutations=["create_issue"],
"- Continuation mode: issue #182 continuation",
"- Existing PR: #186",
"- PR author: jcwalker3",
"- Issue claim status: status:in-progress",
"- Branch: feat/issue-182-controller-handoff",
f"- Old PR head: {self.OLD_SHA}",
f"- New PR head: {self.NEW_SHA}",
"- Session authored PR: yes",
"- Why continuation allowed: operator requested rebase",
])
result = assess_issue_selection_final_report(
report,
mode="continuation",
continuation_proof={
"pr_number": 186,
"pr_author": "jcwalker3",
"issue_number": 182,
"issue_claim_status": "status:in-progress",
"branch": "feat/issue-182-controller-handoff",
"old_head_sha": self.OLD_SHA,
"new_head_sha": self.NEW_SHA,
"session_authored_pr": True,
"continuation_allowed_reason": "operator requested rebase",
},
edited_pr_numbers=[186],
inventoried_pr_numbers=[186],
)
self.assertEqual(result["grade"], "A")
self.assertFalse(result["downgraded"])
def test_missing_controller_handoff_downgrades(self):
report = self._good_report().replace("## Controller Handoff", "")
result = assess_issue_filing_final_report(
class TestContinuationModeProofs(unittest.TestCase):
"""Issue #189: formal continuation proofs beyond #188 selection wall."""
OLD_SHA = PINNED
NEW_SHA = OTHER
REMOTE_OLD = "601c608c00000000000000000000000000000000"
LEASE_SHA = "45c5cac2bc49dd112766ea218b18a71e9c5f8e99"
PUSHED_SHA = "45c5cac2bc49dd112766ea218b18a71e9c5f8e99"
def test_force_with_lease_requires_lease_evidence(self):
report = (
"Issue #182 continuation. force-with-lease push to "
"feat/issue-182-controller-handoff-enforcement."
)
result = assess_force_with_lease_push_report(
report,
issue_number=189,
issue_title=self.ISSUE_TITLE,
mutations=["create_issue"],
resolved_capabilities=self.CAPABILITIES,
issues_searched=12,
performed_mutations=["create_issue"],
old_remote_head=self.REMOTE_OLD,
expected_lease_head=self.LEASE_SHA,
new_pushed_head=self.PUSHED_SHA,
branch_pushed="feat/issue-182-controller-handoff-enforcement",
used_force_with_lease=True,
)
self.assertTrue(result["downgraded"])
def test_abbreviated_sha_downgrades(self):
result = assess_issue_filing_sha_evidence(
f"old head: {self.FULL_SHA[:7]}"
def test_force_with_lease_complete_with_full_evidence(self):
report = (
"Issue #182 continuation with git push --force-with-lease. "
f"Old remote head {self.REMOTE_OLD}. "
f"Lease head {self.LEASE_SHA}. "
f"Pushed head {self.PUSHED_SHA}. "
"Branch feat/issue-182-controller-handoff-enforcement. "
"Push branch only: only the feature branch was pushed."
)
self.assertTrue(result["downgraded"])
def test_mutation_without_capability_proof_downgrades(self):
report = self._good_report().replace("gitea.issue.create", "allowed")
result = assess_issue_filing_mutation_capability(
result = assess_force_with_lease_push_report(
report,
["create_issue"],
self.CAPABILITIES,
old_remote_head=self.REMOTE_OLD,
expected_lease_head=self.LEASE_SHA,
new_pushed_head=self.PUSHED_SHA,
branch_pushed="feat/issue-182-controller-handoff-enforcement",
used_force_with_lease=True,
)
self.assertTrue(result["complete"])
def test_continuation_requires_issue_claim_status(self):
report = (
"Issue #182 continuation mode. PR #186. "
f"old head {self.OLD_SHA} new head {self.NEW_SHA}. "
"PR author jcwalker3. Branch feat/issue-182-test."
)
result = assess_continuation_mode_report(
report,
pr_number=186,
pr_author="jcwalker3",
issue_number=182,
issue_claim_status="status:in-progress",
branch="feat/issue-182-test",
old_head_sha=self.OLD_SHA,
new_head_sha=self.NEW_SHA,
)
self.assertTrue(result["downgraded"])
def test_label_mutation_without_label_proof_downgrades(self):
caps = {
"set_issue_labels": {
"requested_task": "set_issue_labels",
"required_operation_permission": "gitea.issue.comment",
"allowed_in_current_session": True,
},
}
def test_canonical_secret_sweep_helper(self):
sweep = canonical_secret_sweep_report(clean=True)
self.assertEqual(sweep["method"], CANONICAL_SECRET_SWEEP_COMMAND)
self.assertTrue(sweep["clean"])
def test_continuation_final_report_with_push_and_sweep_earns_a(self):
branch = "feat/issue-182-controller-handoff-enforcement"
report = "\n".join([
"Updated labels with gitea.issue.comment capability resolved.",
"Only mutations: set_issue_labels",
"Issue #182 continuation mode — status:in-progress already set.",
f"PR #186 updated via git push --force-with-lease.",
f"Old PR head {self.OLD_SHA}; new PR head {self.NEW_SHA}.",
f"Old remote head {self.REMOTE_OLD}.",
f"Lease head {self.LEASE_SHA}. Pushed head {self.PUSHED_SHA}.",
f"Branch {branch}. Push branch only: only the feature branch.",
"PR author: jcwalker3. Session authored PR: yes.",
"Continuation allowed: operator requested rebase.",
"Open PR inventory included PR #186.",
"## Controller Handoff",
"- Task: continuation",
"- Repo: Scaled-Tech-Consulting/Gitea-Tools",
"- Role: author",
"- Identity: prgs-author",
"- Issue/PR: #182 / PR #186",
f"- Branch/SHA: {branch}",
"- Files changed: review_proofs.py",
"- Validation: tests passed",
"- Mutations: push_branch",
"- Workspace mutations: none",
"- Current status: PR mergeable",
"- Blockers: none",
"- Next: review",
"- Safety: no review/merge",
"- Continuation mode: issue #182 continuation",
"- Existing PR: #186",
"- PR author: jcwalker3",
"- Issue claim status: status:in-progress",
f"- Branch: {branch}",
f"- Old PR head: {self.OLD_SHA}",
f"- New PR head: {self.NEW_SHA}",
"- Session authored PR: yes",
"- Why continuation allowed: operator requested rebase",
])
result = assess_issue_filing_mutation_capability(
report, ["set_issue_labels"], caps
result = assess_issue_selection_final_report(
report,
mode="continuation",
continuation_proof={
"pr_number": 186,
"pr_author": "jcwalker3",
"issue_number": 182,
"issue_claim_status": "status:in-progress",
"branch": branch,
"old_head_sha": self.OLD_SHA,
"new_head_sha": self.NEW_SHA,
"session_authored_pr": True,
"continuation_allowed_reason": "operator requested rebase",
"push_proof": {
"old_remote_head": self.REMOTE_OLD,
"expected_lease_head": self.LEASE_SHA,
"new_pushed_head": self.PUSHED_SHA,
"branch_pushed": branch,
"used_force_with_lease": True,
},
"secret_sweep": canonical_secret_sweep_report(clean=True),
},
edited_pr_numbers=[186],
inventoried_pr_numbers=[186],
)
self.assertTrue(result["downgraded"])
self.assertEqual(result["grade"], "A")
if __name__ == "__main__":