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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
if: steps.base-file.outputs.exists == 'true' && steps.compatibility.outputs.breaking != 'No breaking changes'
env:
BREAKING_APPROVED: ${{ contains(github.event.pull_request.labels.*.name, 'breaking-change-approved') }}
run: node scripts/check-breaking-policy.mjs "origin/${{ github.base_ref }}"
run: node scripts/check-breaking-policy.mjs "origin/${{ github.base_ref }}" "${{ matrix.contract }}"
contracts:
uses: portable-agent/.github/.github/workflows/reusable-node.yml@ed34f26071893063cf70bcd5c784d15d6c4fb5b7
with:
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ OpenAPI в pull request с `main` и блокирует нарушение эт

## Версии и release

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

Версия в `package.json`, OpenAPI и AsyncAPI должна совпадать. Тег `vX.Y.Z` запускает release workflow.
Он проверяет репозиторий и прикладывает к GitHub Release bundle `portable-agent-contracts-X.Y.Z.tgz`.
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.7.0
version: 3.0.0
description: События жизненного цикла безопасных действий.
defaultContentType: application/json
servers:
Expand Down
5 changes: 3 additions & 2 deletions docs/calendar-event.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Создание встречи

Первое разрешённое действие MVP — `calendar.create_event`. Пока его выполняет `fake-calendar`.
Настоящий провайдер позже использует тот же payload.
Первое разрешённое действие MVP — `calendar.create_event`. Для детерминированных тестов его выполняет
`fake-calendar`, а для настоящего календаря — `google-calendar`. Оба connector используют один payload,
поэтому выбор провайдера не меняет форму действия или виджета подтверждения.

## Поля

Expand Down
15 changes: 15 additions & 0 deletions docs/migrations/3.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Переход на contracts 3.0.0

Версия `3.0.0` добавляет connector `google-calendar` в ответы Agent Runtime и Channel Gateway.
Это major-изменение: клиент, который считал `fake-calendar` единственным возможным значением, должен
научиться обрабатывать новый connector до обновления контракта.

## Что изменить

1. Обновить bundle контрактов до `3.0.0`.
2. Добавить обработку `google-calendar` рядом с `fake-calendar`.
3. Не выбирать provider по сетевому адресу. Использовать только имя connector из контракта.
4. Если приложение пока не поддерживает Google Calendar, показывать понятное сообщение вместо падения.

Формат payload встречи не изменился. `fake-calendar` остаётся доступным для локальной разработки и
детерминированных end-to-end тестов.
4 changes: 2 additions & 2 deletions 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.7.0
version: 3.0.0
description: Создание, просмотр и подтверждение безопасных действий агента.
license:
name: Apache-2.0
Expand Down Expand Up @@ -103,7 +103,7 @@ components:
enum: [calendar.create_event]
connector:
type: string
enum: [fake-calendar]
enum: [fake-calendar, google-calendar]
payload:
$ref: "#/components/schemas/CalendarCreateEventPayload"
requestKey:
Expand Down
6 changes: 3 additions & 3 deletions 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.7.0
version: 3.0.0
description: Подготовка предложения действия из текста пользователя.
license:
name: Apache-2.0
Expand Down Expand Up @@ -77,7 +77,7 @@
uniqueItems: true
items:
type: string
enum: [fake-calendar]
enum: [fake-calendar, google-calendar]
default: []
ProposalResponse:
type: object
Expand Down Expand Up @@ -112,7 +112,7 @@
enum: [calendar.create_event]
connector:
type: string
enum: [fake-calendar]
enum: [fake-calendar, google-calendar]

Check failure on line 115 in openapi/agent-runtime-api.yaml

View workflow job for this annotation

GitHub Actions / compatibility (agent-runtime-api)

response-property-enum-value-added

in API POST /api/v1/proposals added the new `google-calendar` enum value to the `proposal/anyOf[#/components/schemas/ActionPlan]/connector` response property for the response status `200`

Check failure on line 115 in openapi/agent-runtime-api.yaml

View workflow job for this annotation

GitHub Actions / compatibility (channel-gateway-api)

response-property-enum-value-added

in API POST /api/v1/messages added the new `google-calendar` enum value to the `proposal/anyOf[#/components/schemas/ActionPlan]/connector` response property for the response status `200`
payload:
$ref: "#/components/schemas/CalendarCreateEventPayload"
explanation:
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.7.0
version: 3.0.0
description: Единый текстовый вход для независимых каналов пользователя.
license:
name: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion openapi/connection-api.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Portable Agent Connection API
version: 2.7.0
version: 3.0.0
description: Подключения внешних аккаунтов. Публичные операции используют владельца из JWT.
license:
name: Apache-2.0
Expand Down
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.7.0
version: 3.0.0
description: Внутренний API состояния диалога и оркестрации предложения действия.
license:
name: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion openapi/mcp-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 MCP Gateway API
version: 2.7.0
version: 3.0.0
description: Безопасный внутренний вызов разрешённых tools в настроенных MCP-сервисах.
license:
name: Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@portable-agent/contracts",
"version": "2.7.0",
"version": "3.0.0",
"private": true,
"files": [
"openapi",
Expand Down
30 changes: 18 additions & 12 deletions scripts/check-breaking-policy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,24 @@ export const checkBreakingPolicy = ({
}
};

export const run = (baseRef, approved) => {
const base = spawnSync(
"git",
["show", `${baseRef}:openapi/action-api.yaml`],
{
encoding: "utf8",
},
);
export const contractPath = (contractName) => {
if (!/^[a-z0-9-]+$/.test(contractName)) {
throw new Error("Некорректное имя OpenAPI-контракта.");
}
return `openapi/${contractName}.yaml`;
};

export const run = (baseRef, approved, contractName = "action-api") => {
const path = contractPath(contractName);
const base = spawnSync("git", ["show", `${baseRef}:${path}`], {
encoding: "utf8",
});
if (base.status !== 0) {
throw new Error(base.stderr || "Не удалось прочитать базовый OpenAPI.");
}

const baseVersion = readVersion(base.stdout);
const nextVersion = readVersion(
readFileSync("openapi/action-api.yaml", "utf8"),
);
const nextVersion = readVersion(readFileSync(path, "utf8"));
checkBreakingPolicy({
baseVersion,
nextVersion,
Expand All @@ -67,5 +69,9 @@ export const run = (baseRef, approved) => {
};

if (process.argv[1]?.endsWith("check-breaking-policy.mjs")) {
run(process.argv[2], process.env.BREAKING_APPROVED === "true");
run(
process.argv[2],
process.env.BREAKING_APPROVED === "true",
process.argv[3],
);
}
39 changes: 37 additions & 2 deletions test/schemas.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import addFormats from "ajv-formats";
import YAML from "yaml";
import {
checkBreakingPolicy,
contractPath,
readVersion,
} from "../scripts/check-breaking-policy.mjs";

const readJson = async (path) => JSON.parse(await readFile(path, "utf8"));

test("breaking policy reads the real OpenAPI version", async () => {
const source = await readFile("openapi/action-api.yaml", "utf8");
assert.equal(readVersion(source), "2.7.0");
assert.equal(readVersion(source), "3.0.0");
});

test("compatibility workflow skips policy when oasdiff finds no breaking changes", async () => {
Expand All @@ -25,6 +26,23 @@ test("compatibility workflow skips policy when oasdiff finds no breaking changes
);
});

test("compatibility workflow checks the contract from the matrix", async () => {
const workflow = await readFile(".github/workflows/ci.yml", "utf8");

assert.match(
workflow,
/check-breaking-policy\.mjs "origin\/\$\{\{ github\.base_ref \}\}" "\$\{\{ matrix\.contract \}\}"/,
);
});

test("breaking policy accepts only a safe contract name", () => {
assert.equal(
contractPath("agent-runtime-api"),
"openapi/agent-runtime-api.yaml",
);
assert.throws(() => contractPath("../package"), /Некорректное имя/);
});

test("breaking change needs approval, new major and migration guide", () => {
assert.throws(
() =>
Expand Down Expand Up @@ -132,14 +150,31 @@ test("action request uses the service request key", async () => {
assert.equal(request.properties.requestKey.type, "string");
assert.equal(request.properties.idempotencyKey, undefined);
assert.deepEqual(request.properties.kind.enum, ["calendar.create_event"]);
assert.deepEqual(request.properties.connector.enum, ["fake-calendar"]);
assert.deepEqual(request.properties.connector.enum, [
"fake-calendar",
"google-calendar",
]);
assert.equal(
request.properties.payload.$ref,
"#/components/schemas/CalendarCreateEventPayload",
);
assert.ok(api.paths["/api/v1/actions"].post.responses["409"]);
});

test("agent can choose fake or Google calendar", async () => {
const source = await readFile("openapi/agent-runtime-api.yaml", "utf8");
const api = YAML.parse(source);
const requestConnectors =
api.components.schemas.UserContext.properties.availableConnectors.items;
const planConnector = api.components.schemas.ActionPlan.properties.connector;

assert.deepEqual(requestConnectors.enum, [
"fake-calendar",
"google-calendar",
]);
assert.deepEqual(planConnector.enum, ["fake-calendar", "google-calendar"]);
});

test("action API uses the shared calendar payload shape", async () => {
const source = await readFile("openapi/action-api.yaml", "utf8");
const api = YAML.parse(source);
Expand Down
Loading