<!-- Backend protocol verification is ENABLED for this project. The Stop hook
     enforces a backend cycle in addition to the browser cycle whenever an
     edited file matches `backend.verifyPatterns`. The cycle is runtime- and
     language-agnostic — it binds to wire protocols, not frameworks. -->

## Backend cycle — runtime- and language-agnostic

The **backend protocol cycle** verifies backend changes by driving real protocol calls (HTTP / gRPC / GraphQL / WebSocket) against the running service and reading the responses, OR by inspecting the logs of the running service (file / docker / kubernetes) when an external driver is hitting the endpoint. It works for ANY backend runtime: Node, Java, Python, Go, Rust, Ruby, .NET, PHP, Elixir, Kotlin, Scala — the agent never attaches to a process.

**This is different from the node cycle.** Node-cycle (`ndt_*`) attaches to a V8 inspector and sets non-blocking probes inside a running Node.js process — it's Node-only. Backend-cycle (`bedt_*`) makes outside-in protocol calls and/or reads logs of any service. They can be active at the same time when both are enabled.

## Backend flow (three evidence paths — at least one is required)

You can satisfy the cycle via **protocol-call evidence** (you drive the request yourself), **log evidence** (something else drives the request, you read the resulting logs), **DB evidence** (you inspect database state directly), or any combination. Pick whichever fits the task; one is enough.

**Batch (speed):** group consecutive `bedt_*` steps into one `mcp__backend-devtools__bedt_execute` — e.g. a POST then a GET that reuses the created id (bind the first call's result: `const r = callTool('bedt_request_http', {…POST…}); callTool('bedt_request_http', { /* GET using an id from r */ })`), register-source + read, or db-connect + query. Keep a step standalone only when you must inspect its result to DECIDE what to do next, not just to pass a value along.

### Path A — Protocol-call evidence

1. **Confirm a backend service is running** (the user's dev server, Docker compose, k8s port-forward, …). The agent itself does not start the service — ask the user if uncertain.
2. **Identify the affected endpoint(s)** for your code change. Look at routes / handlers / controllers in the changed files. Map them to wire-level addresses (URL, gRPC service+method, GraphQL operation name, WebSocket path).
3. **Make a real protocol call** with one of:
   - **HTTP/1.1 + HTTP/2**: `mcp__backend-devtools__bedt_request_http` — full method/headers/body/query/follow-redirects support; ALPN auto-negotiates H2 on TLS, falls back to H1.1.
   - **gRPC**: `mcp__backend-devtools__bedt_request_grpc` — unary + 3 streaming modes; pass `.proto` text or descriptor.
   - **GraphQL**: `mcp__backend-devtools__bedt_request_graphql` — query/mutation, variables, persisted query support.
   - **WebSocket** (stateful — open/send/receive/close cycle):
     - `mcp__backend-devtools__bedt_request_websocket-open` — establish session.
     - `mcp__backend-devtools__bedt_request_websocket-send` / `bedt_request_websocket-receive` — exchange frames.
     - `mcp__backend-devtools__bedt_request_websocket-close` — clean teardown (and `bedt_request_websocket-list` for running sessions).
   - **Replay**: `mcp__backend-devtools__bedt_request_replay` — re-issue a captured curl command or HAR entry.
4. **Inspect the response** — `status` (HTTP / gRPC code), body, headers, returned `traceId` (always W3C `traceparent`).
   **`4xx/5xx and gRPC non-OK are normal results, not errors.** A test for "404 Not Found" SHOULD return 404. Only transport-level failures (DNS, TLS, timeout, abort) populate the response's `error` field. Decide PASS/FAIL based on what your task actually requires.
5. **Chain follow-up calls** if you need to verify side effects (e.g. POST then GET to confirm the new resource is readable). Use `bedt_request_set-default-headers` to pin auth tokens once per host, `bedt_request_set-cookies` for session cookies — both stay scoped to that target across calls.

### Path B — Log evidence (when an external driver hits the endpoint)

Useful when an integration test, the user, or a deploy script is already driving the protocol — your job is to verify "side B" by reading what the server logged. Also a fit for jobs / queue workers / cron handlers where there is no synchronous request to make.

1. **Register the log source** with `mcp__backend-devtools__bedt_log_register-source` — pick `type: "file"` for any process whose stdout is redirected to a file, `type: "docker"` for a container (`container: "<name|id>"`), `type: "kubernetes"` for a pod (`pod`, optionally `kubernetesContainer` / `namespace`). Source names are session-unique; re-register to overwrite. Listing/check helpers: `bedt_log_list-sources`, `bedt_log_check-source`.
2. **Read or follow the source**:
   - `mcp__backend-devtools__bedt_log_read` / `bedt_log_read-multi` — point-in-time read across one or many sources. Filters: `tail` (last N lines), `since` / `until` (ISO-8601 — natively docker; file sources require `parseJson: true` so timestamp is extracted from a JSON field), `pattern` (substring; use for trace-id correlation), `level` (ERROR/WARN/INFO/DEBUG/TRACE/FATAL), `limit`, `parseJson`, `jsonFilter` (dot-path equality predicates against parsed JSON), `contextBefore` / `contextAfter`, `select` (dot-path projection to trim verbose JSONL), `coalesce` (fold multi-line stack traces into one line).
   - `mcp__backend-devtools__bedt_log_follow` — open a streaming subscription that pushes lines into a ring buffer; `bedt_log_get-followed` drains it on demand; `bedt_log_stop-follow` tears it down. Use this when you need to capture logs that emit AFTER your trigger.
3. **Verify the lines you got match the expectation** — error gone, expected log line present, trace-id chained through. Plain-text and JSON sources are both supported; JSON sources accept structural predicates (`jsonFilter: { 'level': 'error', 'route': '/api/orders' }`).
4. **Unregister when done** — `bedt_log_unregister-source` cleans up. Optional; the session tears them down at end too.

### Path C — DB evidence (when the change touches database state)

Best fit for schema migrations, seed-data changes, query-result regressions, and any backend change whose side effect is visible in a relational DB. The agent opens a named, read-only-by-default connection and inspects the state — no need to drive a protocol call when the verification IS reading the DB.

1. **Open a named connection** with `mcp__backend-devtools__bedt_db_connect` — `type: "postgres" | "mysql" | "sqlite"`, `connectionString` or (preferred) `connectionStringEnv`. Default is `allowWrites: false` (server-side READ ONLY mode); only set `allowWrites: true` if you need to seed test data. The session can hold multiple named connections side-by-side (`bedt_db_list-connections` to inspect).
2. **Inspect state** — pick the tool that fits:
   - `mcp__backend-devtools__bedt_db_list-tables` — discover tables.
   - `mcp__backend-devtools__bedt_db_describe-table` — verify schema (columns / types / indexes / FKs) after a migration.
   - `mcp__backend-devtools__bedt_db_query` — run an arbitrary read query (`SELECT count(*) FROM orders WHERE …`, etc.). Always parameterized — the gate honors readonly mode.
   - `mcp__backend-devtools__bedt_db_snapshot` + `bedt_db_diff` — pre/post state diff for verifying that a code path changed exactly the rows it should.
   - `mcp__backend-devtools__bedt_db_watch-changes` + `bedt_db_get-changes` — streaming change capture when a protocol call (or external driver) triggers writes you want to verify after the fact.
3. **(Optional) Seed / migrate** — `bedt_db_seed` (structured) and `bedt_db_run-script` (raw SQL) write data; both require `allowWrites: true` at connect time. Use sparingly and prefer per-test transactions (`bedt_db_transaction-begin` / `-commit` / `-rollback`) so the seed doesn't leak across tests.
4. **Disconnect when done** — `bedt_db_disconnect` releases the connection cleanly. Optional; the session tears them down at end too.

### Trace correlation across paths (`o11y_*` primitives)

The IronBee verification cycle already pins a W3C trace id on every backend tool call via `_metadata.traceId` (see `require-verification`). It outranks any trace pin the agent sets, so you usually don't need the `o11y_*` tools. They are still available when you want to grep logs by trace id, or anchor a multi-tool flow under an explicit id:

- `mcp__backend-devtools__bedt_o11y_new-trace-id` — generate a fresh 32-hex id and pin it on the session (shadowed by IronBee's verification traceId for the cycle).
- `mcp__backend-devtools__bedt_o11y_set-trace-context` — set / clear specific trace-context values.
- `mcp__backend-devtools__bedt_o11y_get-trace-context` — inspect the current pin (useful for `bedt_log_read { pattern: "<traceId>" }`).

### Submit verdict

The verdict is platform-agnostic — `status`, `checks`, and (when applicable) `issues` / `fixes`. The gate enforces that AT LEAST one backend evidence path was exercised in your `bedt_*` tool calls (protocol-call OR log-evidence OR DB-evidence) and that `checks` is non-empty.

```json
{
  "session_id": "<sid>",
  "status": "pass",
  "checks": ["POST /api/orders returned 201 with order id", "GET /api/orders/:id reflects new order"]
}
```

## Multi-cycle (browser + backend, or browser + node + backend)

Common case: a feature edit touches a `.tsx` page (browser-cycle) and a `routes/orders.ts` (node-cycle if Node.js, plus backend-cycle for protocol verification when `backend` is enabled). All active cycles must be satisfied for `status: pass`. **Single** `verification-start`, **single** verdict, **single** retry counter cover all of them. The verdict shape doesn't change with the number of active cycles — same minimal verdict regardless:

```json
{
  "session_id": "<sid>",
  "status": "pass",
  "checks": [
    "checkout page renders",
    "POST /api/orders returned 201",
    "tracepoint at handler.ts:42 fired once",
    "orders table reflects the new row"
  ]
}
```

For a multi-cycle pass, EVERY active cycle's pass criteria must hold.
