Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ jobs:
agent-runtime-api,
channel-gateway-api,
conversation-api,
connection-api,
]
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ OpenAPI в pull request с `main` и блокирует нарушение эт

## Версии и release

Текущая версия — `2.6.0`. Переход с `1.x` описан в
Текущая версия — `2.7.0`. Переход с `1.x` описан в
[migration guide](docs/migrations/2.0.0.md).

Версия в `package.json`, OpenAPI и AsyncAPI должна совпадать. Тег `vX.Y.Z` запускает release workflow.
Expand All @@ -45,6 +45,7 @@ Bundle получает SHA-256 и GitHub artifact attestation. Сервисы
- [Контракт Agent Runtime](docs/agent-runtime.md)
- [Контракт Channel Gateway](docs/channel-gateway.md)
- [Контракт Conversation Service](docs/conversation-service.md)
- [Контракт внешних подключений](docs/connection-service.md)
- [Разработка](docs/development.md)
- [Диагностика](docs/runbook.md)
- [Правила для AI-агентов](AGENTS.md)
Expand Down
2 changes: 1 addition & 1 deletion asyncapi/action-events.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
asyncapi: 3.0.0
info:
title: Portable Agent Action Events
version: 2.6.0
version: 2.7.0
description: События жизненного цикла безопасных действий.
defaultContentType: application/json
servers:
Expand Down
38 changes: 38 additions & 0 deletions docs/connection-service.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Внешние подключения

Контракт `openapi/connection-api.yaml` добавлен в bundle 2.7.0 без изменения существующих запросов.
Это описание будущего API: текущий каркас Connection Service ещё не реализует эти endpoints.

| Метод и путь | Вход | Результат |
| ----------------------------------------- | -------------------------------- | ------------------------------- |
| POST /api/v1/connections/start | provider и пользовательский JWT | URL авторизации и срок ссылки |
| GET /api/v1/connections/callback | state и ровно один из code/error | Статическая страница результата |
| GET /api/v1/connections | Пользовательский JWT | Метаданные своих подключений |
| DELETE /api/v1/connections/{connectionId} | ID и пользовательский JWT | Отключение своего аккаунта |
| POST /internal/v1/tokens | actorId, provider и service JWT | Временный access token |

## Владение и секреты

Публичный API берёт tenant и actor из проверенного JWT. Клиент не задаёт владельца, scope или
redirect URI. Callback не требует пользовательского JWT: владельца определяет сохранённый
одноразовый state, связанный с PKCE. Проверка code XOR error и атомарность state — требования
реализации, которые нельзя выразить только схемой query parameters OpenAPI.

Внутренний API требует audience `connection-service`, scope `connection:token` и разрешённую
сервисную identity. Tenant берётся из JWT; actor передаётся из доверенного контекста действия.
Refresh token не возвращается ни одним endpoint. Ответ с access token и ссылка авторизации имеют
`Cache-Control: no-store`. Query callback и тела ответов с секретами не логируются.

Если подключения нет или нужна повторная авторизация, token endpoint возвращает 409 с problem type
`https://portable-agent.dev/problems/connection_required`. Если подходят несколько аккаунтов —
`https://portable-agent.dev/problems/connection_ambiguous`. Сервис не выбирает аккаунт скрытно.

Первый provider — `google-calendar`. Новые провайдеры добавляются отдельными контрактными изменениями
и адаптерами; произвольные URL и scopes в запросах не поддерживаются.

## Следующие шаги

1. Сгенерировать Java DTO и интерфейсы из опубликованного bundle.
2. Реализовать state/PKCE и зашифрованное хранилище с PostgreSQL-тестами.
3. Проверить callback, refresh и отключение на OAuth stub.
4. Подключить Calendar MCP и сквозной тест, затем реальный Google sandbox.
6 changes: 6 additions & 0 deletions examples/connection.valid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"id": "28efc74e-e82b-4ea2-9143-4dc24c13fe0d",
"provider": "google-calendar",
"status": "active",
"createdAt": "2026-09-27T12:00:00Z"
}
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ nav:
- MCP Gateway API: mcp-gateway.md
- Agent Runtime API: agent-runtime.md
- Channel Gateway API: channel-gateway.md
- Connection Service API: connection-service.md
- Разработка: development.md
- Эксплуатация: runbook.md
- Миграции:
Expand Down
2 changes: 1 addition & 1 deletion openapi/action-api.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Portable Agent Action API
version: 2.6.0
version: 2.7.0
description: Создание, просмотр и подтверждение безопасных действий агента.
license:
name: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion openapi/agent-runtime-api.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Portable Agent Runtime API
version: 2.6.0
version: 2.7.0
description: Подготовка предложения действия из текста пользователя.
license:
name: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion openapi/channel-gateway-api.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Portable Agent Channel Gateway API
version: 2.6.0
version: 2.7.0
description: Единый текстовый вход для независимых каналов пользователя.
license:
name: Apache-2.0
Expand Down
267 changes: 267 additions & 0 deletions openapi/connection-api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
openapi: 3.1.0
info:
title: Portable Agent Connection API
version: 2.7.0
description: Подключения внешних аккаунтов. Публичные операции используют владельца из JWT.
license:
name: Apache-2.0
identifier: Apache-2.0
servers:
- url: https://api.portable-agent.dev/connections
security:
- bearerAuth: []
paths:
/api/v1/connections:
get:
tags: [Connections]
operationId: listConnections
summary: Получить подключения текущего пользователя
responses:
"200":
description: Подключения только для tenant и sub проверенного JWT
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Connection"
"401":
$ref: "#/components/responses/Problem"
/api/v1/connections/start:
post:
tags: [Connections]
operationId: startConnection
summary: Начать подключение внешнего аккаунта
description: Создаёт одноразовый state и PKCE. Redirect URI и scopes берутся из конфигурации сервера.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/StartRequest"
responses:
"200":
description: Ссылка для открытия в браузере; ответ нельзя кешировать или логировать
headers:
Cache-Control:
$ref: "#/components/headers/NoStore"
content:
application/json:
schema:
$ref: "#/components/schemas/StartResponse"
"400":
$ref: "#/components/responses/Problem"
"401":
$ref: "#/components/responses/Problem"
/api/v1/connections/callback:
get:
tags: [Connections]
operationId: completeConnection
summary: Обработать возврат OAuth-провайдера
description: |
Владельца определяет одноразовый state из серверного хранилища.
Требуется ровно один из code или error. State проверяется и атомарно погашается,
включая отказ пользователя. Code обменивается с сохранённым PKCE verifier.
Ответ не содержит токенов; code и state запрещено писать в access logs.
security: []
parameters:
- name: state
in: query
required: true
schema:
type: string
minLength: 32
maxLength: 512
- name: code
in: query
schema:
type: string
minLength: 1
maxLength: 4096
- name: error
in: query
schema:
type: string
minLength: 1
maxLength: 256
responses:
"200":
description: Статическая страница результата без внешнего redirect и секретов
headers:
Cache-Control:
$ref: "#/components/headers/NoStore"
content:
text/html:
schema:
type: string
"400":
$ref: "#/components/responses/Problem"
"502":
$ref: "#/components/responses/Problem"
/api/v1/connections/{connectionId}:
delete:
tags: [Connections]
operationId: disconnectConnection
summary: Отключить своё подключение
description: |
Немедленно запрещает локальную выдачу токенов.
Credentials удаляются после provider revoke. При временной ошибке возвращается 502,
подключение остаётся отключённым; повтор DELETE повторяет revoke.
Для чужого или отсутствующего подключения возвращается 404.
parameters:
- name: connectionId
in: path
required: true
schema:
type: string
format: uuid
responses:
"204":
description: Подключение отключено
"401":
$ref: "#/components/responses/Problem"
"404":
$ref: "#/components/responses/Problem"
"502":
$ref: "#/components/responses/Problem"
/internal/v1/tokens:
post:
tags: [Tokens]
operationId: getConnectionToken
summary: Получить короткоживущий токен для выполнения действия
description: |
Требует service JWT с audience connection-service, scope connection:token
и разрешённым client identity. Tenant берётся из проверенного JWT.
Actor приходит только из доверенного контекста Action Service.
Нет активного подключения — 409 connection_required.
Несколько подходящих подключений — 409 connection_ambiguous; скрытый выбор запрещён.
security:
- serviceAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TokenRequest"
responses:
"200":
description: Только access token; тело запрещено логировать
headers:
Cache-Control:
$ref: "#/components/headers/NoStore"
content:
application/json:
schema:
$ref: "#/components/schemas/TokenResponse"
"400":
$ref: "#/components/responses/Problem"
"401":
$ref: "#/components/responses/Problem"
"403":
$ref: "#/components/responses/Problem"
"409":
$ref: "#/components/responses/Problem"
"502":
$ref: "#/components/responses/Problem"
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: Пользовательский JWT; tenant_id и sub определяют владельца.
serviceAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: Service JWT с audience connection-service и scope connection:token.
headers:
NoStore:
required: true
schema:
type: string
const: no-store
responses:
Problem:
description: Ошибка без секретов и внутренних данных
content:
application/problem+json:
schema:
$ref: "#/components/schemas/Problem"
schemas:
StartRequest:
type: object
additionalProperties: false
required: [provider]
properties:
provider:
type: string
enum: [google-calendar]
StartResponse:
type: object
additionalProperties: false
required: [url, expiresAt]
properties:
url:
type: string
format: uri
description: Ссылка авторизации настроенного провайдера.
expiresAt:
type: string
format: date-time
Connection:
type: object
additionalProperties: false
required: [id, provider, status, createdAt]
properties:
id:
type: string
format: uuid
provider:
type: string
enum: [google-calendar]
status:
type: string
enum: [active, reconnect_required, disconnected]
createdAt:
type: string
format: date-time
TokenRequest:
type: object
additionalProperties: false
required: [actorId, provider]
properties:
actorId:
type: string
format: uuid
provider:
type: string
enum: [google-calendar]
TokenResponse:
type: object
additionalProperties: false
required: [accessToken, tokenType, expiresAt]
properties:
accessToken:
type: string
minLength: 1
tokenType:
type: string
enum: [Bearer]
expiresAt:
type: string
format: date-time
Problem:
type: object
additionalProperties: false
required: [type, title, status]
properties:
type:
type: string
format: uri-reference
title:
type: string
status:
type: integer
minimum: 400
maximum: 599
2 changes: 1 addition & 1 deletion openapi/conversation-api.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Portable Agent Conversation API
version: 2.6.0
version: 2.7.0
description: Внутренний API состояния диалога и оркестрации предложения действия.
license:
name: Apache-2.0
Expand Down
Loading
Loading