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
18 changes: 9 additions & 9 deletions QA-CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -630,16 +630,16 @@
> ⚠️ Every bullet below needs `LANGFLOW_A2A_ENABLED=true` on the instance — the flag is off by default (`lfx/services/settings/groups/mcp.py`), and with it off all three `/api/v1/a2a/*` routes answer `404` and the flow editor's Agent tab only renders "A2A is turned off on this server". Surface map, testability decisions and the full out-of-scope list: `docs/core-functionality/a2a/a2a-coverage-scope.md` (scoping issue #1195, upstream `langflow-ai/langflow#13831`, Jira epic `LE-1588`). Numbering starts at 16 because §12–§15 are already taken; the section sits here, with its area, on purpose.

#### 16.1 A2A Server
- [-] Agent card served for a published flow — `GET /api/v1/a2a/{flow_id}/.well-known/agent-card.json` returns `protocolVersion="0.3.0"`, `url` ending in `/api/v1/a2a/{flow_id}/jsonrpc`, `capabilities.streaming=true`, `defaultInputModes=["application/json"]`, `skills[0].id === flow_id`, `skills[0].tags=["langflow"]` and an `inputSchema` object → `core-functionality/a2a/a2a-server-agent-card.spec.ts`
- [-] Card overrides applied — `a2a_card_overrides` (name / version / description / tags / examples) change exactly those card fields and nothing else → `core-functionality/a2a/a2a-server-agent-card.spec.ts`
- [-] Card gated on publication state — `a2a_enabled=false`, `flow_type=workflow` and an unknown flow id each return `404` (indistinguishable from an unmounted route, by design) → `core-functionality/a2a/a2a-server-agent-card.spec.ts`
- [-] Agent discovery list — `GET /api/v1/a2a/agents` lists the published flow with a `cardUrl` that resolves `200`, omits a `flow_type=workflow` flow and an `agent` flow with `a2a_enabled=false`, and drops the row when the flow is unpublished (owner-scoped, not a cross-user directory) → `core-functionality/a2a/a2a-server-discovery.spec.ts`
- [-] JSON-RPC `message/send` round-trip — a per-run sentinel sent to a Chat Input→Chat Output passthrough comes back in the task's artifact text with state `completed` (no LLM involved) → `core-functionality/a2a/a2a-server-jsonrpc-message-send.spec.ts`
- [-] JSON-RPC error envelopes — an unknown method returns `-32601` and a malformed envelope `-32600`/`-32700`, both over **HTTP 200** (JSON-RPC-level errors are not HTTP errors here) → `core-functionality/a2a/a2a-server-jsonrpc-message-send.spec.ts`
- [-] Multi-turn context continuity — the first `message/send` response carries a server-minted `contextId`; reusing it on a second call returns the same `contextId` with a new task id and lands in the same stored session (`session_id` is the composite `<uuid>:<contextId>` in `GET /api/v1/monitor/messages`, carrying both turns as `User`/`Machine` pairs), while a call without it mints a different one → `core-functionality/a2a/a2a-server-multi-turn-context.spec.ts`
- [-] Task lifecycle — `tasks/get` reads the `message/send` task back with the same `artifactId` and `status.timestamp` (a read-back, not a re-run); an unknown id is `-32001 "Task not found"` and a cancel on a finished task `-32002 "Task cannot be canceled"` with the stored state untouched, both over **HTTP 200**; a task id cancelled through another flow's endpoint is `-32001` (never `-32002`, which would confirm it exists); and a `message/stream` run cancelled mid-flight reports `canceled` from both `tasks/cancel` and `tasks/get` → `core-functionality/a2a/a2a-server-tasks-lifecycle.spec.ts`
- [x] Agent card served for a published flow — `GET /api/v1/a2a/{flow_id}/.well-known/agent-card.json` returns `protocolVersion="0.3.0"`, `url` ending in `/api/v1/a2a/{flow_id}/jsonrpc`, `capabilities.streaming=true`, `defaultInputModes=["application/json"]`, `skills[0].id === flow_id`, `skills[0].tags=["langflow"]` and an `inputSchema` object → `core-functionality/a2a/a2a-server-agent-card.spec.ts`
- [x] Card overrides applied — `a2a_card_overrides` (name / version / description / tags / examples) change exactly those card fields and nothing else → `core-functionality/a2a/a2a-server-agent-card.spec.ts`
- [x] Card gated on publication state — `a2a_enabled=false`, `flow_type=workflow` and an unknown flow id each return `404` (indistinguishable from an unmounted route, by design) → `core-functionality/a2a/a2a-server-agent-card.spec.ts`
- [x] Agent discovery list — `GET /api/v1/a2a/agents` lists the published flow with a `cardUrl` that resolves `200`, omits a `flow_type=workflow` flow and an `agent` flow with `a2a_enabled=false`, and drops the row when the flow is unpublished (owner-scoped, not a cross-user directory) → `core-functionality/a2a/a2a-server-discovery.spec.ts`
- [x] JSON-RPC `message/send` round-trip — a per-run sentinel sent to a Chat Input→Chat Output passthrough comes back in the task's artifact text with state `completed` (no LLM involved) → `core-functionality/a2a/a2a-server-jsonrpc-message-send.spec.ts`
- [x] JSON-RPC error envelopes — an unknown method returns `-32601` and a malformed envelope `-32600`/`-32700`, both over **HTTP 200** (JSON-RPC-level errors are not HTTP errors here) → `core-functionality/a2a/a2a-server-jsonrpc-message-send.spec.ts`
- [x] Multi-turn context continuity — the first `message/send` response carries a server-minted `contextId`; reusing it on a second call returns the same `contextId` with a new task id and lands in the same stored session (`session_id` is the composite `<uuid>:<contextId>` in `GET /api/v1/monitor/messages`, carrying both turns as `User`/`Machine` pairs), while a call without it mints a different one → `core-functionality/a2a/a2a-server-multi-turn-context.spec.ts`
- [x] Task lifecycle — `tasks/get` reads the `message/send` task back with the same `artifactId` and `status.timestamp` (a read-back, not a re-run); an unknown id is `-32001 "Task not found"` and a cancel on a finished task `-32002 "Task cannot be canceled"` with the stored state untouched, both over **HTTP 200**; a task id cancelled through another flow's endpoint is `-32001` (never `-32002`, which would confirm it exists); and a `message/stream` run cancelled mid-flight reports `canceled` from both `tasks/cancel` and `tasks/get` → `core-functionality/a2a/a2a-server-tasks-lifecycle.spec.ts`
- [ ] API-key auth gate on the JSON-RPC endpoint — with the flow's project set to `auth_type=apikey`, the card advertises the `x-api-key` scheme in `securitySchemes`, a call with no header returns `401 "API key required"`, a wrong key `401 "Invalid API key"`, and the owner's key `200` + `completed` (the gate `LE-2081` lives behind; auth derives from the **project**, not the flow)
- [-] Agent tab publish flow — a blank flow shows `Unavailable` with the copy "Add a chat input and output to serve this flow." and cannot publish; adding Chat Input + Chat Output (unwired — the two node types are the gate) enables `agent-publish-switch`; publishing shows status `Live` and the `agent-card-url` that `404`d as a draft now fetches `200`; editing Name and adding a tag then pressing `agent-save` updates `agent-card-name` **and** changes `name` / `skills[0].name` / `skills[0].tags` on the card the API serves. The "Agent updated" toast is **not** asserted — measured transient (<3 s), so the durable pair is the status chip plus the served card → `core-functionality/a2a/a2a-server-agent-tab-publish.spec.ts`
- [x] Agent tab publish flow — a blank flow shows `Unavailable` with the copy "Add a chat input and output to serve this flow." and cannot publish; adding Chat Input + Chat Output (unwired — the two node types are the gate) enables `agent-publish-switch`; publishing shows status `Live` and the `agent-card-url` that `404`d as a draft now fetches `200`; editing Name and adding a tag then pressing `agent-save` updates `agent-card-name` **and** changes `name` / `skills[0].name` / `skills[0].tags` on the card the API serves. The "Agent updated" toast is **not** asserted — measured transient (<3 s), so the durable pair is the status chip plus the served card → `core-functionality/a2a/a2a-server-agent-tab-publish.spec.ts`
- [~] Agent tab "Try it" panel — a sentinel sent from the panel over the live endpoint appears in `agent-transcript` **twice** (user turn + the agent's echo), the state reaches `completed`, the turn counter reads `1 turn` and a `Reset` control is present → `core-functionality/a2a/a2a-server-agent-tab-try-it.spec.ts`. **Partial:** "View JSON-RPC exchange" is not covered because it is not implemented — `agentTab.viewExchange` appears exactly once per locale bundle in the shipped frontend (the dictionary entry) with zero call sites, and the string is absent from the DOM after a completed turn (measured on `1.12.0.dev14`, #1244); pending an upstream question
- [ ] Non-owner cannot publish a flow — `PATCH /api/v1/flows/{id}` flipping `a2a_enabled` returns `403 "Cannot change a2a_enabled of a flow you do not own."` (not automatable: needs two real users, and per-test user isolation is impossible under `AUTO_LOGIN` — measured in #1010)
- [ ] Disabled-server state — all three `/api/v1/a2a/*` routes `404` and the Agent tab renders "A2A is turned off on this server. Set LANGFLOW_A2A_ENABLED=true…" (out of scope by lane decision on #1195: the flag is on in every lane, and an off-lane cannot coexist with it in the same run; revisit only as a dedicated `PW_A2A_OFF=1` lane)
Expand Down
9 changes: 5 additions & 4 deletions docs/core-functionality/a2a/a2a-server-agent-card.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# A2A Server — agent card: spec-valid fields, overrides and publication gating

**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev14`)
**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev18`)

**Issue:** #1242 · **Scoped by:** #1195 → `a2a-coverage-scope.md` (row **T2**) ·
**Depends on:** #1240 (`LANGFLOW_A2A_ENABLED=true` on every lane) ·
Expand Down Expand Up @@ -40,14 +40,15 @@ they discover the **wrong** contract — a `url` that does not answer, a

## Tags *(required)*

`@api` `@release` `@a2a`
`@stable` `@api` `@release` `@a2a`

- `@api` — drives `/api/v1/flows/` and `/api/v1/a2a/*` via `request`, no UI.
- `@release` — the card is the happy path of publishing a flow as an agent; a
broken card breaks A2A wholesale.
- `@a2a` — new functional tag for this area, added to `CLAUDE.md` by #1242.
- **No `@stable` yet:** granted only after team validation (`CONTRIBUTING.md`).
This spec is new in #1242 and has no daily history.
- `@stable` — validated by the team and promoted in #1349: the batch ran
**51/51 green** (17 tests × 3, `--retries=0`) on nightly `1.12.0.dev18`,
with no leaked flow and no backend error logged.

---

Expand Down
9 changes: 5 additions & 4 deletions docs/core-functionality/a2a/a2a-server-agent-tab-publish.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# A2A Server — Agent tab: eligibility gate, publishing, and the card editor

**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev14`)
**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev18`)

**Issue:** #1244 · **Scoped by:** #1195 → `a2a-coverage-scope.md` (row **U1**) ·
**Depends on:** #1240 (`LANGFLOW_A2A_ENABLED=true` on every lane), #1242 / PR #1243
Expand Down Expand Up @@ -44,15 +44,16 @@ the spec cannot degrade into a DOM echo of itself.

## Tags *(required)*

`@release` `@workspace` `@ui-ux` `@a2a`
`@stable` `@release` `@workspace` `@ui-ux` `@a2a`

- `@release` — publishing a flow as an agent is the happy path of the whole A2A
feature; if this breaks, A2A is unusable regardless of how healthy the API is.
- `@workspace` — drives the flow editor and canvas (component add, tab switch).
- `@ui-ux` — the assertions are about what the interface renders and enables.
- `@a2a` — functional area; requires `LANGFLOW_A2A_ENABLED=true` (`CLAUDE.md`).
- **No `@stable` yet:** granted only after team validation (`CONTRIBUTING.md`).
New in #1244, no daily history.
- `@stable` — validated by the team and promoted in #1349: the batch ran
**51/51 green** (17 tests × 3, `--retries=0`) on nightly `1.12.0.dev18`,
with no leaked flow and no backend error logged.

---

Expand Down
9 changes: 5 additions & 4 deletions docs/core-functionality/a2a/a2a-server-agent-tab-try-it.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# A2A Server — Agent tab: the "Try it" panel round-trips over the live endpoint

**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev14`)
**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev18`)

**Issue:** #1244 · **Scoped by:** #1195 → `a2a-coverage-scope.md` (row **U3**) ·
**Depends on:** #1240 (`LANGFLOW_A2A_ENABLED=true` on every lane), #1242 / PR #1243
Expand Down Expand Up @@ -33,16 +33,17 @@ sentinel only once (the user turn, with no reply), fails.

## Tags *(required)*

`@workspace` `@ui-ux` `@a2a`
`@stable` `@workspace` `@ui-ux` `@a2a`

- `@workspace` — drives the flow editor.
- `@ui-ux` — the subject is what the panel renders after a send.
- `@a2a` — functional area; requires `LANGFLOW_A2A_ENABLED=true` (`CLAUDE.md`).
- **No `@release`:** publishing is the release-critical path and carries the tag
(`a2a-server-agent-tab-publish.spec.ts`); "Try it" is a confirmation affordance,
not a gate on shipping.
- **No `@stable` yet:** granted only after team validation (`CONTRIBUTING.md`).
New in #1244, no daily history.
- `@stable` — validated by the team and promoted in #1349: the batch ran
**51/51 green** (17 tests × 3, `--retries=0`) on nightly `1.12.0.dev18`,
with no leaked flow and no backend error logged.

---

Expand Down
8 changes: 5 additions & 3 deletions docs/core-functionality/a2a/a2a-server-discovery.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# A2A Server — agent discovery list: what `/api/v1/a2a/agents` does and does not enumerate

**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev14`)
**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev18`)

**Issue:** #1242 · **Scoped by:** #1195 → `a2a-coverage-scope.md` (row **T3**) ·
**Depends on:** #1240 · **Jira:** epic `LE-1588`, registry `LE-1719`
Expand Down Expand Up @@ -33,14 +33,16 @@ orchestrator wires itself to a dead endpoint.

## Tags *(required)*

`@api` `@a2a`
`@stable` `@api` `@a2a`

- `@api` — pure REST, no UI.
- `@a2a` — this area's functional tag (added by #1242).
- **No `@release`:** discovery is not on the critical publish path — a caller
that already knows the flow id reaches the card directly. The card and
`message/send` specs carry `@release`.
- **No `@stable` yet** — pending team validation.
- `@stable` — validated by the team and promoted in #1349: the batch ran
**51/51 green** (17 tests × 3, `--retries=0`) on nightly `1.12.0.dev18`,
with no leaked flow and no backend error logged.

---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# A2A Server — JSON-RPC `message/send`: round-trip and error envelopes

**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev14`)
**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev18`)

**Issue:** #1242 · **Scoped by:** #1195 → `a2a-coverage-scope.md` (row **T4**) ·
**Depends on:** #1240 · **Jira:** epic `LE-1588`, transport `LE-1805`,
Expand Down Expand Up @@ -36,12 +36,14 @@ where `LE-2081` (RCE on the public A2A endpoint) lived.

## Tags *(required)*

`@api` `@release` `@a2a`
`@stable` `@api` `@release` `@a2a`

- `@api` — REST/JSON-RPC only, no UI.
- `@release` — running a published agent is the feature; nothing ships if this is red.
- `@a2a` — area tag (added by #1242).
- **No `@stable` yet** — pending team validation.
- `@stable` — validated by the team and promoted in #1349: the batch ran
**51/51 green** (17 tests × 3, `--retries=0`) on nightly `1.12.0.dev18`,
with no leaked flow and no backend error logged.

---

Expand Down
9 changes: 5 additions & 4 deletions docs/core-functionality/a2a/a2a-server-multi-turn-context.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# A2A Server — multi-turn context: a conversation keeps its thread

**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev14`)
**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev18`)

**Issue:** #1247 · **Scoped by:** #1195 → `a2a-coverage-scope.md` (row **T5**) ·
**Depends on:** #1240 (`LANGFLOW_A2A_ENABLED=true` on every lane), #1242 / PR #1243
Expand Down Expand Up @@ -34,13 +34,14 @@ Three things have to hold, and each one alone is insufficient:

## Tags *(required)*

`@api` `@a2a`
`@stable` `@api` `@a2a`

- `@api` — drives `/api/v1/a2a/{id}/jsonrpc` and `/api/v1/monitor/messages` through
`request`; no UI.
- `@a2a` — functional area; requires `LANGFLOW_A2A_ENABLED=true` (`CLAUDE.md`).
- **No `@stable` yet:** granted only after team validation (`CONTRIBUTING.md`).
New in #1247, no daily history.
- `@stable` — validated by the team and promoted in #1349: the batch ran
**51/51 green** (17 tests × 3, `--retries=0`) on nightly `1.12.0.dev18`,
with no leaked flow and no backend error logged.

---

Expand Down
8 changes: 5 additions & 3 deletions docs/core-functionality/a2a/a2a-server-tasks-lifecycle.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# A2A Server — task lifecycle: read back, cancel, and fail closed

**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev14`)
**Last validated:** Langflow 1.12.x (nightly `1.12.0.dev18`)

**Issue:** #1247 · **Scoped by:** #1195 → `a2a-coverage-scope.md` (row **T6**) ·
**Depends on:** #1240 (`LANGFLOW_A2A_ENABLED=true` on every lane), #1242 / PR #1243
Expand Down Expand Up @@ -41,13 +41,15 @@ Two behaviours are safety properties rather than conveniences:

## Tags *(required)*

`@api` `@regression` `@a2a`
`@stable` `@api` `@regression` `@a2a`

- `@api` — drives `/api/v1/a2a/{id}/jsonrpc` through `request`; no UI.
- `@regression` — the spec-code mapping is a **fix** that can regress to -32603,
and the cross-flow gate is a leak that was closed deliberately.
- `@a2a` — functional area; requires `LANGFLOW_A2A_ENABLED=true` (`CLAUDE.md`).
- **No `@stable` yet:** granted only after team validation (`CONTRIBUTING.md`).
- `@stable` — validated by the team and promoted in #1349: the batch ran
**51/51 green** (17 tests × 3, `--retries=0`) on nightly `1.12.0.dev18`,
with no leaked flow and no backend error logged.

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ async function publish(
return res.json();
}

test.describe("A2A Server — agent card @api @release @a2a", () => {
test("published agent flow serves a spec-valid card @api @release @a2a", async ({ request }) => {
test.describe("A2A Server — agent card", () => {
test("published agent flow serves a spec-valid card", { tag: ["@stable", "@release", "@api", "@a2a"] }, async ({ request }) => {
const headers = { Authorization: await getAuthToken(request) };
await requireA2aEnabled(request, headers);

Expand Down Expand Up @@ -77,7 +77,7 @@ test.describe("A2A Server — agent card @api @release @a2a", () => {
}
});

test("card overrides change exactly what the card advertises @api @a2a", async ({ request }) => {
test("card overrides change exactly what the card advertises", { tag: ["@stable", "@api", "@a2a"] }, async ({ request }) => {
const headers = { Authorization: await getAuthToken(request) };
await requireA2aEnabled(request, headers);

Expand Down Expand Up @@ -122,7 +122,7 @@ test.describe("A2A Server — agent card @api @release @a2a", () => {
}
});

test("card is 404 while the flow is not published @api @a2a", async ({ request }) => {
test("card is 404 while the flow is not published", { tag: ["@stable", "@api", "@a2a"] }, async ({ request }) => {
const headers = { Authorization: await getAuthToken(request) };
await requireA2aEnabled(request, headers);

Expand Down Expand Up @@ -152,7 +152,7 @@ test.describe("A2A Server — agent card @api @release @a2a", () => {
}
});

test("card is 404 for an unknown flow id @api @a2a", async ({ request }) => {
test("card is 404 for an unknown flow id", { tag: ["@stable", "@api", "@a2a"] }, async ({ request }) => {
const headers = { Authorization: await getAuthToken(request) };
await requireA2aEnabled(request, headers);

Expand Down
Loading
Loading