- Upgrade SKILL.md §K compact format to the issue #182 canonical field set
(Task/Repo/Role/Identity/Issue-PR/Branch-SHA/Files/Validation/Mutations/
Current status/Blockers/Next/Safety) plus role-specific field lists for
review/merge, author, and queue/inventory tasks.
- Point the review-pr, merge-pr, and start-issue template handoff lines at
the exactly-titled Controller Handoff section with their role fields.
- Add review_proofs.assess_controller_handoff(): reports without the exact
section are 'missing' (downgraded), present-but-partial are 'incomplete'
with the absent fields listed, and role extras are enforced per role.
- Add TestControllerHandoff (8 tests) including a SKILL.md doc-contract
test so the documented requirement cannot silently rot.
The handoff supplements the full report; full-report validation rules are
unchanged.
Closes#182
Co-Authored-By: Claude Fable 5 <[email protected]>
- Replace raw patch("sys.stdout") and contextlib.redirect_stderr with pytest MonkeyPatch or contextlib redirect scoped to main calls in CLI tests (create_*, prs, python_cli, manage_labels, merge_pr, review_pr).
- Add regression tests in test_review_proofs.py for stdout/stderr remaining usable.
- Ensures normal pytest summary output without junitxml workaround; improves review validation reporting per #173.
- No behavior change to production code or gates.
Refs #178
Adds review_proofs.py with pure, fail-closed helpers for the reviewer-side
blind queue workflow:
- verify_pinned_head_checkout: proves HEAD == pinned PR head (full 40-hex,
no prefix matches) and diff base == PR base; any mismatch blocks
review/merge.
- assess_inventory_completeness: exhaustive "only PRs found" claims require
every configured repo listed with stated filters, proven pagination, and
reported open-PR totals.
- assess_validation_report: validation results are unclaimable unless the
exact command was stated and its output read; missing pass/fail/skip
counts, unjustified ignored paths, or unexplained deviation from the
canonical command downgrade the report to weak.
- assess_self_review_contamination: contamination must be evidence-backed;
bare same-session claims (either way) yield "unknown" with a
choose-another-PR-or-stop action, never an assumed verdict.
- build_final_report: distinguishes identity eligibility, author/reviewer
distinctness, session contamination, validation-on-pinned-head, merge,
and issue verification; grades "A" only when every proof is present,
downgrades otherwise, and marks a merge without allowing proofs as a
blocked violation.
tests/test_review_proofs.py covers all seven harness assertions from the
issue (unchecked-out pinned head blocks merge; multi-repo inventory;
pagination; missing counts flagged weak; evidence-required contamination;
unread output cannot claim validation passed; ignored paths need
justification).
SKILL.md section F and templates/review-pr.md now require the checkout,
inventory, validation-evidence, and contamination proofs and the
distinguished final report. No review/merge safety gate is weakened.
Closes#173
Co-Authored-By: Claude Fable 5 <[email protected]>
Covers:
1. Stale prior handoff merged but live open
2. Prior open but live closed
3. list_prs and view_pr disagree
4. Merge commit missing after claimed merge
5. Linked issue remains open after claimed merge
Uses live mocks for list/view with staleness fields (#166).
Proves coverage; no src changes or gate weakening.
Refs #170
- Add gitea_get_pr_review_feedback: read-only MCP-native discovery of
formal PR review verdicts (APPROVED / REQUEST_CHANGES / COMMENT) with
latest-state-per-reviewer, blocking change-request and approval
summaries, reviewed-head vs current-head staleness, dismissed/PENDING
handling, credential redaction of review bodies, no endpoint URLs
without the reveal opt-in, and a fail-closed gitea.read gate that
returns feedback_not_attempted with a structured #142 permission
report and zero API calls.
- Add task/role guards to gitea_resolve_task_capability: new
stop_required and task_role_guidance outputs, and a distinct
address_pr_change_requests author task, so a review/merge request
under an author profile returns explicit STOP guidance instead of
silently degrading into author-side mutations, and author-side
remediation is explicitly barred from review verdicts and merges.
- Add build_validation_report: validation reporting helper that
distinguishes passed / failed / skipped / not-run, requires the exact
output for failures, rejects vague statuses, and never implies
full-suite success unless the full-suite command passed.
- Document task/role alignment (review vs author-fix vs merge vs
comment workflows with required identity, allowed/forbidden actions,
and stop conditions), MCP-native review feedback discovery, and
validation reporting discipline in docs/llm-workflow-runbooks.md.
- Add tests/test_review_feedback.py (22 tests) covering discovery,
staleness, redaction, URL hiding, fail-closed gating, role guards for
author/reviewer profiles, and validation report semantics.
Closes#167
Co-Authored-By: Claude Fable 5 <[email protected]>
- Inventory report now includes 'Repository: org/repo' name
- Inventory failure path redacts exception via _redact() per project pattern; no raw exception text leaked
- Added/updated tests covering:
* repository name present in inventory output
* inventory failure output is redacted
* raw exception text does not leak
* author profiles can perform read-only PR inventory
* author profiles still cannot approve/request_changes/merge or bypass gates
Preserves all hard gates and author restrictions. No other scope.
Refs #164
- Updated jenkins-readonly and glitchtip-readonly skills in _PROJECT_SKILLS:
- Descriptions and notes now use actual server names (jenkins-mcp, glitchtip-mcp)
- Explain historical -readonly skill names vs registration in mcp-control-plane #55
- Note gated trigger in jenkins-mcp (see #56), partial filing in glitchtip (see #57)
- Updated docs/safety-model.md: naming note, corrected mutation gating claims for trigger/filing
- Updated docs/architecture/jenkins-readonly-build-status-design.md: naming note, updated Phase 1 to account for gated trigger
- No behavior change to skill status/availability; no secrets exposed
- AC: stale naming explained, actual names documented, Jenkins claims corrected, GlitchTip filing as partial, no over-claiming
Closes#154
- mcp_get_control_plane_guide now instructs to ALWAYS resolve task capability first (gitea_resolve_task_capability), verify identity/profile, use correct namespace.
- Added hard stops for resolve-first, namespace separation, issue.comment distinct from pr.comment.
- Added/updated skills: gitea-resolve-task-capability (new), and steps/notes in issue-authoring, pr-creation, pr-review, pr-merge, issue-comments to call resolver first.
- Guide docstring and guidance updated for AC: do not hardcode, resolve before act, reviewer for review/merge, author for authoring, issue comments require gitea.issue.comment.
- Tests: 4 new covering guide for author/reviewer, missing issue-comment, static-profile safe next action; EXPECTED_SKILLS updated.
- No secrets/URLs; synthetic tests; no prod behavior change.
Closes#144
- Fix allowedTools options inside run_compliance to support prefixless and prefixed tool access
- Implement sys.executable resolution to allow compliance runner to run safely inside git worktrees
- Dynamically load GITEA_MCP_CONFIG JSON profiles inside gitea_auth.py to support 'mock' remote name
- Implement gitea_url helper inside gitea_auth.py to automatically handle HTTP and HTTPS scheme differences for localhost/loopback mock targets, preventing wrong SSL version errors
- Fix verdict.py escaping mismatch by normalizing stream-json block output structure before matching strings, allowing PR view decision points to be verified correctly
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Every gated denial now carries a 'permission_report' explaining itself:
requested/missing/required operation, active profile and identity (when
already resolved), the profile's allowed operations, which configured
profiles could perform the operation (names only), whether runtime
switching is supported, whether a different MCP namespace/session is
required, and the exact safe next action.
New fail-soft helper _permission_block_report builds the report only
after a gate has refused — it adds guidance to denials, never widens a
permission, performs no network I/O, and degrades to a minimal
fail-closed report if the profile itself cannot be resolved. Wired into
gitea_check_pr_eligibility (and via it gitea_submit_pr_review,
gitea_merge_pr, and the legacy gitea_review_pr wrapper) and into the
issue-comment gate consumers gitea_list_issue_comments and
gitea_create_issue_comment. Input errors (e.g. empty comment body) are
not permission failures and get no report.
Tests cover: missing gitea.issue.comment, author attempting review and
merge, reviewer attempting an authoring operation, unknown/missing
profile fail-closed, switching-enabled guidance, eligible calls carry
no report, and no secret/URL material in any report.
Closes#142
Co-Authored-By: Claude Fable 5 <[email protected]>
- Add 5 new tests covering AC from #145:
- issue.comment does not imply close_issue
- pr.comment does not imply issue.comment (via explicit forbidden)
- reviewer profile cannot push_branch
- structured guidance for missing merge permission
- self-approve (self-review gate) blocked with reasons
- Tests are fully synthetic (patches, no live Gitea)
- Updated file docstring
- All 10 tests pass; no behavior changes to prod code
Closes#145
Implements issue #156 after the first skill-comply run reported 100%
compliance while every scenario died at HTTP 401 before the review/merge
decision point.
- compliance/safety.py: loopback-only target rail; refuses live Gitea
hosts and all non-loopback addresses; IPs validated via ipaddress
(a string-prefix check would accept DNS names like 127.0.0.1.evil.com);
no environment override.
- compliance/mock_gitea.py: in-memory loopback mock Gitea (whoami, PR
view/list, review, merge, branch delete) recording every mutation;
token via env reference only.
- compliance/specs/gitea-workflow.json + compliance/spec.py: pinned spec
with all eight critical merge workflow steps required; fail-closed
loader and drift detection against generated specs.
- compliance/verdict.py: deterministic three-way verdicts. Runs blocked
before the decision point are INCONCLUSIVE, never compliant; auto-merge
without explicit approval, blind merge, merge without review, missing
explicit remote, mutation after auth failure, and live-host mutation
are NONCOMPLIANT. Positive behaviors (explicit remote, fail-closed on
auth failure, no live mutations) are recorded.
- compliance/run_compliance.py: orchestrator running three pinned
scenarios via claude -p against the mock; the competing scenario passes
only by reaching the decision point and refusing to merge.
- compliance/results/2026-07-05-skill-comply-smoke-test.md: reclassifies
the original 100% report as smoke-test evidence only.
- tests/test_compliance_harness.py: 43 tests covering the safety rail
(including dotted-127 bypass attempts), pinned spec, drift, mock
server, verdicts, scenario config generation, and report rendering.
Closes#156
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Record the #139 accepted decision as a deployment doc: two static
per-role MCP namespaces (gitea-author, gitea-reviewer), one credential
per process, with dynamic profile switching and dispatcher routing
explicitly rejected for now. Covers rationale (audit clarity, credential
concentration, two-party review boundary, fail-closed behavior),
reference-only client setup via GITEA_MCP_CONFIG/GITEA_MCP_PROFILE,
the 'Auth unsupported' client-badge caveat, and reconnect/reload
requirements after profile/config/code changes. Cross-linked from the
execution-profiles model doc and the workflow runbooks; guarded by
docs-content tests.
Closes#143
Co-Authored-By: Claude Fable 5 <[email protected]>
PR #138 added the issue.comment alias and example grants. Add the
forward-direction separation guards the migration decision requires:
gitea.issue.comment never implies issue close, PR review/approve/merge,
or branch push / repo commit; gitea.pr.comment never implies
gitea.issue.comment; the pre-#137 live author op set still fails closed
with the exact operator-facing reason; and the shipped v1/v2 example
configs keep granting issue comments while preserving author/reviewer
role separation.
Refs #137, #139
Co-Authored-By: Claude Fable 5 <[email protected]>
- Add aliases 'issue.comment' and 'issue_comment' to GITEA_OPERATION_ALIASES so short names normalize to gitea.issue.comment
- Grant 'issue.comment' (and short) in v2 example profiles for example-author and example-reviewer
- Grant in gitea-mcp.example.json mdcps-reviewer
- Update docs example profiles
- Add test coverage for new aliases and normalization
This enables gitea_create_issue_comment and list for profiles that include it, while preserving fail-closed for others and separation of duties (issue.comment does not imply pr.review etc).
Closes#137
Subagent review (read-only) found no blockers; this addresses its one
LOW note by documenting that the helper mutates the passed dict and
must receive a freshly-built one.
Co-Authored-By: Claude Fable 5 <[email protected]>