docs: define task-scoped Gitea MCP execution profile model (#12) #21
@@ -0,0 +1,215 @@
|
|||||||
|
# Gitea MCP Execution Profiles
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This document defines the **task-scoped execution profile model** for the
|
||||||
|
`gitea-mcp` package of the MCP Control Plane. It describes *what a profile is*,
|
||||||
|
the metadata each profile carries, and the safety rules that govern which
|
||||||
|
profile may perform which Gitea operation.
|
||||||
|
|
||||||
|
This issue defines the **model only**. It does not implement runtime profile
|
||||||
|
loading, profile switching, or any review/merge behavior — see
|
||||||
|
[Relationship to roadmap issues](#relationship-to-roadmap-issues).
|
||||||
|
|
||||||
|
## Principle: LLMs are not roles
|
||||||
|
|
||||||
|
The central rule of this model:
|
||||||
|
|
||||||
|
```text
|
||||||
|
The LLM is not the role.
|
||||||
|
The MCP credential/profile used for the task is the role.
|
||||||
|
```
|
||||||
|
|
||||||
|
An LLM session is not permanently an "author," a "reviewer," or a "merger."
|
||||||
|
Any LLM session may perform any of these roles — but only while operating
|
||||||
|
through a **task-appropriate execution profile** whose authenticated Gitea
|
||||||
|
identity and allowed operations fit the task.
|
||||||
|
|
||||||
|
Consequences:
|
||||||
|
|
||||||
|
- A task selects a profile; a profile is not assigned to a model.
|
||||||
|
- The same LLM may act as author for one task and reviewer for another, by
|
||||||
|
using different profiles — never by escalating a single profile.
|
||||||
|
- Two roles that must not be held by one identity (e.g. author and merger of
|
||||||
|
the same PR) are separated by using **different authenticated identities**,
|
||||||
|
not by trusting the LLM to behave.
|
||||||
|
|
||||||
|
## Profile model
|
||||||
|
|
||||||
|
A Gitea MCP execution profile is a named, declarative description of an
|
||||||
|
authenticated capability set. Each profile defines the following fields:
|
||||||
|
|
||||||
|
| Field | Type | Meaning |
|
||||||
|
|-------|------|---------|
|
||||||
|
| `profile_name` | string | Stable identifier, e.g. `gitea-reviewer`. |
|
||||||
|
| `authenticated_username` | string | The Gitea login this profile authenticates as (verified at runtime via `gitea_whoami`, not trusted from config). |
|
||||||
|
| `allowed_operations` | list | Operation categories this profile may perform. |
|
||||||
|
| `forbidden_operations` | list | Operation categories this profile must never perform. |
|
||||||
|
| `token_source_name` | string | The *name* of the secret source (e.g. env var name or secret key). **Never the token value.** |
|
||||||
|
| `audit_label` | string | Short label attached to audit records for actions by this profile. |
|
||||||
|
| `can_approve_prs` | bool | May submit an approving PR review. |
|
||||||
|
| `can_merge_prs` | bool | May merge a PR. |
|
||||||
|
| `can_push_branches` | bool | May push branches / create commits. |
|
||||||
|
| `can_mutate_issues` | bool | May create/edit/label/close issues and comment. |
|
||||||
|
| `can_author_impl_prs` | bool | May author implementation PRs (branch + commit + open PR). |
|
||||||
|
|
||||||
|
`token_source_name` records **where** a token comes from (a variable or key
|
||||||
|
name), never the token itself. Token values are never part of a profile object,
|
||||||
|
never logged, never returned by a tool, and never committed.
|
||||||
|
|
||||||
|
## Example profiles
|
||||||
|
|
||||||
|
The following are the reference profiles. Booleans express intended capability
|
||||||
|
boundaries; they are the model, not a runtime enforcement mechanism yet.
|
||||||
|
|
||||||
|
### `gitea-issue-manager`
|
||||||
|
|
||||||
|
- **allowed:** `read`, `issue.create`, `issue.comment`, `issue.label`, `issue.close`
|
||||||
|
- **forbidden:** `pr.approve`, `pr.merge`, `branch.push`
|
||||||
|
- `can_approve_prs`: `false`
|
||||||
|
- `can_merge_prs`: `false`
|
||||||
|
- `can_push_branches`: `false`
|
||||||
|
- `can_mutate_issues`: `true`
|
||||||
|
- `can_author_impl_prs`: `false`
|
||||||
|
|
||||||
|
### `gitea-author`
|
||||||
|
|
||||||
|
- **allowed:** `read`, `branch.push`, `pr.create`, `pr.comment`, `issue.comment`
|
||||||
|
- **forbidden:** `pr.approve`, `pr.merge`
|
||||||
|
- `can_approve_prs`: `false`
|
||||||
|
- `can_merge_prs`: `false`
|
||||||
|
- `can_push_branches`: `true`
|
||||||
|
- `can_mutate_issues`: `false` (may comment, may not manage)
|
||||||
|
- `can_author_impl_prs`: `true`
|
||||||
|
|
||||||
|
### `gitea-reviewer`
|
||||||
|
|
||||||
|
- **allowed:** `read`, `pr.comment`, `pr.review`, `pr.approve`, `pr.request_changes`
|
||||||
|
- **forbidden:** `pr.merge`, `branch.push`
|
||||||
|
- `can_approve_prs`: `true`
|
||||||
|
- `can_merge_prs`: `false`
|
||||||
|
- `can_push_branches`: `false`
|
||||||
|
- `can_mutate_issues`: `false`
|
||||||
|
- `can_author_impl_prs`: `false`
|
||||||
|
|
||||||
|
### `gitea-merger`
|
||||||
|
|
||||||
|
- **allowed:** `read`, `pr.merge`
|
||||||
|
- **forbidden:** `pr.approve`, `branch.push`, `pr.create`
|
||||||
|
- `can_approve_prs`: `false` (a merger must not also be the sole approver)
|
||||||
|
- `can_merge_prs`: `true`
|
||||||
|
- `can_push_branches`: `false`
|
||||||
|
- `can_mutate_issues`: `false`
|
||||||
|
- `can_author_impl_prs`: `false`
|
||||||
|
|
||||||
|
### `gitea-owner`
|
||||||
|
|
||||||
|
- **allowed:** broad administrative access; use sparingly and never for routine
|
||||||
|
LLM workflow tasks.
|
||||||
|
- **forbidden:** nothing structurally, which is exactly why it must not be the
|
||||||
|
default profile for automated work.
|
||||||
|
- `can_approve_prs`: `true`
|
||||||
|
- `can_merge_prs`: `true`
|
||||||
|
- `can_push_branches`: `true`
|
||||||
|
- `can_mutate_issues`: `true`
|
||||||
|
- `can_author_impl_prs`: `true`
|
||||||
|
|
||||||
|
> `gitea-owner` exists for human/administrative use. Automated LLM workflows
|
||||||
|
> should prefer the narrowest sufficient profile. An all-powerful profile is a
|
||||||
|
> convenience, not a role, and it does not exempt a session from the
|
||||||
|
> self-review / self-merge rule below.
|
||||||
|
|
||||||
|
## Allowed and forbidden operations
|
||||||
|
|
||||||
|
Operations are grouped into coarse categories so profiles stay readable:
|
||||||
|
|
||||||
|
- `read` — view issues, PRs, files, identity (`gitea_whoami`).
|
||||||
|
- `issue.*` — `create`, `comment`, `label`, `close`.
|
||||||
|
- `pr.*` — `create`, `comment`, `review`, `approve`, `request_changes`, `merge`.
|
||||||
|
- `branch.push` — push branches / create commits.
|
||||||
|
|
||||||
|
Rules:
|
||||||
|
|
||||||
|
- `forbidden_operations` always wins over `allowed_operations`. If an operation
|
||||||
|
appears in both, it is forbidden.
|
||||||
|
- An operation not present in `allowed_operations` is treated as **not
|
||||||
|
allowed** (deny by default).
|
||||||
|
- These categories are descriptive for this issue. Their runtime enforcement is
|
||||||
|
out of scope here (see roadmap links).
|
||||||
|
|
||||||
|
## Identity and fail-closed rules
|
||||||
|
|
||||||
|
Before **any** mutating action, a workflow must know both:
|
||||||
|
|
||||||
|
1. **The active profile** — which profile is in effect for this task.
|
||||||
|
2. **The authenticated identity** — the real Gitea login, verified via
|
||||||
|
`gitea_whoami` (issue #11), not read from configuration and trusted.
|
||||||
|
|
||||||
|
Fail-closed requirements:
|
||||||
|
|
||||||
|
- If the active profile is unknown → **stop; do not mutate.**
|
||||||
|
- If the authenticated identity cannot be determined → **stop; do not mutate.**
|
||||||
|
- If the requested operation is not in the profile's `allowed_operations`, or is
|
||||||
|
in `forbidden_operations` → **stop; do not mutate.**
|
||||||
|
- Ambiguity is treated as denial. The safe default is always "do not act."
|
||||||
|
|
||||||
|
Read-only actions may proceed without a resolved profile, but must still never
|
||||||
|
expose token or credential material.
|
||||||
|
|
||||||
|
## Self-review and self-merge prevention
|
||||||
|
|
||||||
|
A profile/session **must not approve or merge a PR authored by the same
|
||||||
|
authenticated Gitea user.**
|
||||||
|
|
||||||
|
- The check compares the profile's *verified* `authenticated_username`
|
||||||
|
(from `gitea_whoami`) against the **PR author**.
|
||||||
|
- If they match, `pr.approve` and `pr.merge` fail closed, regardless of what the
|
||||||
|
profile's capability booleans say.
|
||||||
|
- This is why author and merger/reviewer roles are separated by **identity**,
|
||||||
|
not by prompt or by a single escalating profile. It is also why this was the
|
||||||
|
concrete blocker discovered while dogfooding PR #8 for issue #52.
|
||||||
|
|
||||||
|
## Token and secret handling
|
||||||
|
|
||||||
|
- Token **values** are never logged, never returned by any tool, and never
|
||||||
|
committed to the repository.
|
||||||
|
- Profiles reference a `token_source_name` (a variable/key *name*) only.
|
||||||
|
- `Authorization` headers and raw credentials must never appear in tool output,
|
||||||
|
audit records, or error messages.
|
||||||
|
|
||||||
|
## Separation from other MCP boundaries
|
||||||
|
|
||||||
|
`gitea-mcp` profile work stays within the Gitea trust boundary. It must **not**
|
||||||
|
add or absorb Jenkins, Ops, GlitchTip, Release, deploy, rollback, migration,
|
||||||
|
restart, or production behavior. Those belong to their own MCP packages under
|
||||||
|
the "one server per trust boundary" model described in
|
||||||
|
[`tool-boundaries.md`](tool-boundaries.md) and
|
||||||
|
[`credential-isolation.md`](credential-isolation.md).
|
||||||
|
|
||||||
|
## Relationship to roadmap issues
|
||||||
|
|
||||||
|
This document defines the **model only**. Related work is tracked separately
|
||||||
|
under roadmap [#10](https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools/issues/10):
|
||||||
|
|
||||||
|
- **#11** — Authenticated-user identity lookup (`gitea_whoami`). *Complete;
|
||||||
|
this model depends on it for verified identity.*
|
||||||
|
- **#19** — Runtime profile configuration via environment (loading real
|
||||||
|
profiles/tokens). *Not this issue.*
|
||||||
|
- **#13** — Read-only profile discovery (exposing the active profile). *Not this
|
||||||
|
issue.*
|
||||||
|
- **#14** — PR author / reviewer eligibility checks. *Not this issue.*
|
||||||
|
- **#15** — Gated PR review/approve actions. *Not this issue.*
|
||||||
|
- **#16** — Gated PR merge workflow. *Not this issue.*
|
||||||
|
- **#18** — Audit logging of mutating actions with profile metadata. *Not this
|
||||||
|
issue.*
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- Do **not** implement runtime profile switching or selection here.
|
||||||
|
- Do **not** implement multi-token loading here.
|
||||||
|
- Do **not** implement approve, merge, or eligibility workflows here.
|
||||||
|
- Do **not** expose, log, or commit any token or secret.
|
||||||
|
- Do **not** add Jenkins, Ops, GlitchTip, Release, deploy, or production
|
||||||
|
behavior.
|
||||||
|
- Do **not** create an all-powerful server; `gitea-owner` is administrative, not
|
||||||
|
a default automation role.
|
||||||
Reference in New Issue
Block a user