# Pull Request Subscription Extension Surface

> Canonical path: `specs/app/provider-watch-agent-mcp.md`
> Status: living-spec
> Scope: GitHub/Gitea pull request subscription actions exposed through App UI
> and App-hosted Extensions.
>
> Related specs:
> - [`specs/extension/extension-platform.md`](../extension/extension-platform.md)
> - [`specs/service/service-actor-platform.md`](../service/service-actor-platform.md)
> - [`specs/service/service-event-delivery.md`](../service/service-event-delivery.md)
>
## 1. Purpose

GitHub and Gitea Apps let humans and authorized Agents subscribe a AgentTeam target
to pull request updates. The App owns the external-service connection and
service-event rows. Its enabled Extension exposes that capability to the Agent
through the flattened Extension Catalog:

```text
agentteam exec github watch-pull-request
agentteam exec gitea watch-pull-request
```

Extension is not a Service Kind and does not create a second App Installation,
connection, authorization, or credential record.

## 2. Management surfaces

```text
App settings UI
agentteam exec github|gitea <action>
```

Both surfaces resolve the installed App service actor and operate on its
`service_event_subscriptions`. Agent invocation must pass the authoritative
Extension preflight before reaching the shared pull-request-watch service:

```text
authenticated Agent
  -> pinned Catalog version/digest
  -> active Extension selection
  -> host App Installation
  -> effective App authorization and connection
  -> registered Definition and Action
  -> in-process pull-request-watch service
```

Human `@github` or `@gitea` mentions are not long-lived subscription management
surfaces. Mention handlers may guide users to the App UI or the authorized
Extension.

## 3. Action surface

GitHub and Gitea each register the same closed action set under their own
Extension Definition:

```text
watch-pull-request
list-watches
pause-watch
resume-watch
unwatch
```

The provider comes from the selected Extension Definition; it is not an input
hint and is never inferred from another installed App. Both providers call one
provider-neutral service, but neither provider package imports the other.

Create input identifies the AgentTeam target and pull request and may select event
filters and principal notification. Lifecycle input identifies a watch by
`watchId` or by the exact target plus pull request. List input may filter by
target, resource, and status and uses bounded cursor pagination. Input and
output schemas use `lowerCamelCase`.

`watch-pull-request` validates an explicitly supplied non-empty event list
against the shared provider-neutral vocabulary; an explicit empty list is
invalid rather than an alias for the omitted/default filter. Before writing a
subscription it resolves repository authority from the exact selected Service
installation. GitHub additionally verifies that its installation can read the
pull request. Gitea treats a ready repository or organization connection as the
authority for the inbound webhook subscription; it does not require an
otherwise unnecessary pull-request-read PAT scope after the connection has
already verified and installed the webhook. A canonical `resource` emitted by
`list-watches` is accepted as filter input and must preserve provider identity
during normalization. A target plus pull-request lifecycle selector must
resolve exactly one active watch; multiple matches return `conflict` and the
caller must retry with `watchId`.

## 4. Target and ownership

Provider-watch target, binding, and authorization boundaries stay separate.
Target, App binding, Agent authorization, and provider connection are separate
boundaries. A target is valid only when the Agent can see it and the installed
App can create or reuse the matching target binding. The subscription stores
the resolved target and target snapshot, so webhook delivery never depends on
later prompt context.

The service-event actor records the authenticated Agent/member and resolves the
human authorizer from the effective App authorization. An invocation for one
App Installation or provider must not read or mutate another installation's
watch.

## 5. Result boundary

The Agent-visible result is a compact management projection, not a serialization
of the backing service-event row. It may include:

```text
watchId
provider
target
pullRequest
resource
status
events
createdAt
updatedAt
hasMore
nextCursor
```

It must not expose internal target refs, permission or delivery scopes, App
Installation or connection refs, credentials, member or actor ids, correlation
ids, provider headers, or raw storage metadata. Builtin execution returns the
same bounded Extension result envelope and mandatory audit outcome as External
Extensions.

## 6. Event delivery

```text
provider webhook
  -> service_webhook_receipt
  -> service_event_subscription match
  -> service_event_delivery
  -> service-authored message or task discussion
```

The visible author is the GitHub/Gitea service member. The message source is:

```text
kind = provider_event
refType = service_event_delivery
```

## 7. Retired Agent runtime surfaces

The retired Exec PR-watch command family and private provider-app runtime
config/tool-call routes do not exist.

New code must not add a bridge, app-scoped token exchange, self-HTTP call, or
request/response alias layer for these retired paths. Watch ids are canonical service-event subscription ids; unknown or removed
ids fail closed.

## 8. Agent confirmation and diagnostics

After creating or reusing a subscription, an Agent briefly confirms the watched
pull request and AgentTeam target and includes the stable watch id when available.
It does not invent a target or provider resource. Ambiguous or missing input
fails validation rather than selecting another provider or App.

Connection problems, authorization failures, blocked targets, rate limits,
unsupported filters, and malformed pull request refs return bounded structured
errors. Failures after webhook receipt belong in the service-event delivery
ledger; management validation failures do not create deliveries.
