fix(adapters): to_openapi runtime-truthful projection (PRJ-01..05, PRJ-11..15)

- /search, /schema: document the envelope wrapper and the real item/spec
  fields (PRJ-01/02); /search drops unreachable 401/403, documents 404
  (PRJ-15)
- error statuses: 422 for dispatch-path INVALID_INPUT /
  INVALID_OPERATION_TYPE; extractor 400s documented as the plain-text
  gap they are (PRJ-03); operation-declared errors projected by
  http_status with x-runtime-behavior: 500 on non-HTTP_* codes (PRJ-04
  project-honest decision) — no runtime changes
- /subscribe: 200+SSE only; event:error terminal contract documented
  (PRJ-05, GW-12)
-  components for requests/responses; CallRequest no longer inlined
  per-path (PRJ-14); all library expect() paths removed (PRJ-11)
- error projections folded into BTreeMaps: same registry =>
  byte-identical doc, sorted enums (PRJ-12)
- components.securitySchemes.bearerAuth + top-level security (PRJ-15)
- info.version 1.1.0 -> 1.2.0 (ADR-045 minor: additive documentation of
  the settled runtime contract)
- 31 unit tests incl. golden print-level assertions mirroring the routes
  tests' actual bodies and a determinism test

Verification: cargo test (288), clippy --all-targets -D warnings, fmt
--check, cargo doc --no-deps, cargo test --all-features (all green in a
clean worktree at HEAD; shared tree carries parallel agents' edits).
This commit is contained in:
2026-08-29 12:47:10 +00:00
parent 509850de3d
commit 4ef8499bbb
4 changed files with 1166 additions and 704 deletions
+20 -16
View File
@@ -395,7 +395,7 @@ surface problem).
| OpenAPI path | Call protocol | HTTP method | Purpose |
|--------------|--------------|-------------|---------|
| `/search` | `services/list` | `GET` | List/search operations (AccessControl-filtered). Names + descriptions. |
| `/search` | `services/list` | `GET` | List the caller's callable operations (AccessControl-filtered). Items: `name`, `namespace`, `op_type`. |
| `/schema` | `services/schema` | `GET` | Get an operation's full `OperationSpec`. |
| `/call` | `call.requested` (Query/Mutation) | `POST` | Invoke an operation. Flat JSON body `{ operation, input }`. |
| `/batch` | multiple `call.requested` | `POST` | Invoke multiple operations. Array of `{ operation, input }`. |
@@ -473,27 +473,31 @@ number:
```
`to_openapi` projects `error_schemas` to the gateway endpoint's
response definitions. The `/call` endpoint's responses include the
operation-level errors (mapped by `http_status`), plus the protocol-
level errors:
response definitions. The `/call` endpoint's responses are the shared
components-referenced status map: the protocol-level errors, plus any
operation-level errors (keyed by the registry's declared `http_status`;
entries whose codes lack the `HTTP_<status>` prefix carry
`x-runtime-behavior: 500` — the runtime mapper is purely code-driven,
see review-001 PRJ-04):
```yaml
# /call endpoint responses
responses:
'200': { schema: <output_schema for the called operation> }
'400': { schema: <INVALID_INPUT error> }
'401': { schema: <no bearer token> }
'403': { schema: <FORBIDDEN — insufficient scopes> }
'404': { schema: <NOT_FOUND — operation not registered or Internal> }
'422': { schema: <operation-level error with http_status=422> }
'429': { schema: <operation-level error with http_status=429> }
'500': { schema: <INTERNAL> }
'504': { schema: <TIMEOUT> }
'200': { $ref: '#/components/schemas/CallOk' } # envelope { request_id, result, output }
'400': { description: plain-text extractor rejection (framework body) }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'422': { oneOf: [CallErrorInvalidInput, CallErrorInvalidOperationType] }
'429': { ... } # only when ops declare errors at 429; x-runtime-behavior: 500 for non-HTTP_* codes
'500': { oneOf: [CallErrorInternal, CallFailure] } # INTERNAL + non-HTTP_* operation codes
'503': { ... } # only when ops declare errors at 503
'504': { $ref: '#/components/responses/Timeout' }
```
The operation-level errors (with `http_status`) are surfaced on the
`/call` endpoint's response — the gateway propagates the called
operation's `error_schemas` as response definitions. This makes the
The operation-declared errors are surfaced on the `/call` and
`/publish` endpoints' responses — the gateway projects the registered
operations' `error_schemas` as response definitions. This makes the
adapter contract from alkcall ADR-022 faithful on the error axis — no
silent dropping of error contracts. See alkcall ADR-016.
+1144 -686
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -764,7 +764,7 @@ mod tests {
assert!(text.contains("application/json"), "got: {text}");
// The 6-endpoint gateway doc with the version from the /publish addition.
assert!(text.contains("\"/publish\""), "publish path in doc");
assert!(text.contains("1.1.0"), "info.version 1.1.0 in doc");
assert!(text.contains("1.2.0"), "info.version 1.2.0 in doc");
assert!(text.contains("gatewayPublish"), "publish operationId");
let _ = server_task.await;
+1 -1
View File
@@ -265,7 +265,7 @@ async fn full_surface_gateway_over_http() {
.unwrap();
assert_eq!(resp.status(), 200);
let body: serde_json::Value = resp.json().await.unwrap();
assert_eq!(body["info"]["version"], "1.1.0");
assert_eq!(body["info"]["version"], "1.2.0");
assert!(body["paths"].get("/publish").is_some());
assert!(body["paths"].get("/call").is_some());
}