- 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
- 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]>
Add three read-only capability-discovery tools so new LLM sessions can
learn the workflow rules and available project skills from the MCP
server instead of long pasted operator prompts:
- mcp_get_control_plane_guide: active profile, authenticated identity
(fail-soft; unresolved identity returns STOP instructions),
allowed/forbidden operations, profile-aware guidance (author profiles
are told review/approve/merge is forbidden; reviewer profiles are told
review/merge requires eligibility checks and a pinned head SHA; mixed
profiles get a misconfiguration warning), and the standing rules: hard
stops, fail-closed behavior, head-SHA pinning, merge confirmation,
redaction, author/reviewer/merger separation, profile switching, and
identity verification.
- mcp_list_project_skills: registry of ten project workflows (issue
authoring, PR creation, PR review, PR merge, issue comments, profile
switching, redaction/security review, Jenkins read-only, GlitchTip
read-only, release/operator) with description, when-to-use, required
operations, status, and per-profile availability. Unimplemented
services are listed as designed-not-implemented rather than omitted.
- mcp_get_skill_guide: step-by-step guide per skill; unknown names fail
closed with the list of valid names.
All three are read-only and change no existing gate or permission.
Normal output contains no endpoint URLs or keychain IDs; the guide
includes the server host only under GITEA_MCP_REVEAL_ENDPOINTS=1.
Tests (tests/test_operator_guide.py, 17 new): profile-aware guidance
for author/reviewer, unresolved-identity STOP, read-only behavior,
redaction defaults and reveal opt-in, rules coverage, registry
completeness and profile awareness, unimplemented-service marking,
fail-closed unknown skill names.
Docs: llm-workflow-runbooks.md now tells new sessions to call the guide
tools first.
Closes#128
Co-Authored-By: Claude Fable 5 <[email protected]>
Add gitea_list_issue_comments and gitea_create_issue_comment so
discussion/design workflows can read and post issue comments through
the MCP layer instead of direct API scripts.
- List requires gitea.read; create requires gitea.issue.comment —
gated separately from the gitea.pr.* review/merge family, fail closed.
- Issue comments never touch PR review endpoints.
- LLM-safe output: comment id/author/timestamps/body only; web links
appear solely under the GITEA_MCP_REVEAL_ENDPOINTS admin opt-in.
- Create operations are audit-logged (create_issue_comment) and errors
are redacted before being raised.
- Tests cover list/create success, permission blocks (including PR
review permissions not granting issue comments), forbidden-overrides,
empty body, missing issue with redacted error, endpoint separation,
and reveal opt-in.
- Document issue comments versus PR reviews in
docs/gitea-execution-profiles.md.
Closes#126
Co-Authored-By: Claude Fable 5 <[email protected]>
Promote the #103 minimal alias map to the documented public table
GITEA_OPERATION_ALIASES and add the #106 enforcement layer:
- normalize_operation(op, service): canonical namespaced names; legacy
spellings accepted only via the explicit table; unknown, ambiguous,
and cross-service names fail closed.
- check_operation(op, allowed, forbidden, service): normalizes BOTH the
requested operation and the profile lists before any membership
check; forbidden always overrides allowed; unnormalizable allowed
entries grant nothing and unnormalizable forbidden entries deny the
request, so normalization can never silently widen permissions;
empty/missing allowed list denies everything.
- gitea_check_pr_eligibility now routes its capability check through
check_operation, fixing the mismatch where canonical namespaced
profile ops (gitea.pr.merge) never matched the raw action (merge)
and namespaced forbidden entries were never enforced.
- Document the normalization table and enforcement rules in
docs/gitea-execution-profiles.md, replacing the stale 'enforcement
out of scope' caveat.
- tests/test_op_normalization.py: full #106 matrix (27 tests) —
qualified/legacy allowed and forbidden, unknown, ambiguous, service
mismatch, forbidden-overrides-allowed, empty/missing allowed,
duplicates after normalization, no permission widening, and
eligibility integration proving normalization happens before
enforcement. Existing v1/env unqualified behaviour stays compatible.
Closes#106
Co-Authored-By: Claude Fable 5 <[email protected]>
Support the canonical contexts-shape version 2 config (contexts / profiles /
projects / rules) alongside the existing environments shape and v1:
- Require a boolean 'enabled' on every context, profile, service, and
project. Disabled entries are surfaced in audits but fail closed at
selection/resolution — never a silent fallback to another profile,
service, or credential source.
- Resolve the active identity from GITEA_MCP_PROFILE via the existing
select_profile path; profile base_url falls back to the context's enabled
gitea block.
- Add resolve_service() and project_for_path() for context service and
project-to-context resolution (internal use; fail closed on disabled).
- get_auth_header now propagates ConfigError when GITEA_MCP_CONFIG is set
instead of silently degrading to Basic auth.
- Hide endpoint URLs and keychain ids from normal LLM-facing output:
gitea_whoami / gitea_get_profile report logical names and auth status
only; new gitea_audit_config tool reports enabled/disabled state and safe
one-line service summaries. The GITEA_MCP_REVEAL_ENDPOINTS opt-in (and
'python3 gitea_config.py audit --reveal-endpoints' locally) restores
endpoints and auth source names for admin diagnostics; token values are
never printed on any path.
- Ship gitea-mcp.v2-contexts.example.json (synthetic values) and validate
it in tests.
Implements #120
Co-Authored-By: Claude Fable 5 <[email protected]>