Files
alkhttp/docs/architecture/decisions/066-from-jsonschema-as-http-adapter.md
T
glm-5.3-flash 320ea87b08 docs: port architecture specs and ADRs from alknet-http; write new alkhttp ADRs 067-070
Phase 1 (SDD) — architecture documentation:

Ported specs (adapted for alkcall, producer/consumer terms, 6-endpoint
gateway, channels-over-WS, Sub/Pub operation types):
- overview.md, http-server.md, http-adapters.md, http-mcp.md
- README.md index (rewritten for alkhttp)

New ADRs:
- 067: WebSocket carries the channels protocol (8-byte chunk demux,
  channel 0 = alk/call, upgrade path /alk/channels)
- 068: gateway /publish endpoint for Pub operations (NDJSON body)
- 069: WebTransport out of scope in alkhttp (alknet concern)
- 070: from_wss consumer adapter (wss feature, tokio-tungstenite)

Ported ADRs (25, same numbers, port notes + amendments where the
extraction changed facts): 001-004, 010, 014, 015, 017, 022, 023, 027,
034, 036, 037, 039, 041, 042, 044, 045, 046, 047, 048, 049, 051, 066.

websocket.md rewritten for the channels session; open-questions.md
seeded (OQ-01 WS byte-stream adapter, OQ-02 /publish framing,
OQ-03 from_wss reconnect, OQ-04 browser client ownership).

Verified: cargo test, clippy -D warnings, fmt, doc --no-deps.
2026-08-27 14:19:24 +00:00

13 KiB

ADR-066: from_jsonschema as an HTTP-Backed Single-Endpoint Adapter in alkhttp

Ported from alknet ADR-066 (from_jsonschema as an HTTP-Backed Single-Endpoint Adapter in alknet-http); re-targeted to alkhttp.

Status

Accepted (supersedes the from_jsonschema clause of ADR-017 §5 and the FromJsonSchema provenance row of ADR-022 — both described a schema-only, no-handler adapter in the call crate)

Context

from_jsonschema was originally specified (alknet ADR-017 §5) as a schema-only adapter living in the call crate (alknet-call, now alkcall): it produced HandlerRegistration bundles with a NOT_FOUND-returning placeholder handler and FromJsonSchema provenance. The stated use case was validation, discovery, and composition-graph construction without a runtime — type-checking a composition plan without executing it, building a UI of available operations without standing up the transports.

This is broken. An operation in the OperationRegistry needs a real handler. A placeholder that returns NOT_FOUND does not work with how the registry is supposed to function: an Internal op registered with a dead handler is a trap, not a feature. The "schema-only, no handler" concept conflated two things — schema validation (a compile-time / planning activity that doesn't need a registry entry at all) and operation registration (which always needs a handler). Validation against a JSON Schema does not require a HandlerRegistration; it requires the schema and a validator. Registering an operation requires a handler. The old from_jsonschema tried to do the former by abusing the latter, and produced something that works for neither.

The misplacement was compounded by a location error: the adapter lived in the call crate (which is supposed to stay lean — no HTTP client), but a from_jsonschema that is actually useful for calling non-standard endpoints needs reqwest, exactly like from_openapi and from_mcp. The adapter location map in ADR-017 / the call crate's client-and-adapters.md already establishes that HTTP-backed adapters live in the HTTP crate; the old from_jsonschema violated its own stated principle by living in the call crate. The move was recorded as alknet ADR-066 (moved from alknet-call to alknet-http); with the extraction, that makes it this crate — alkhttp. The FromJsonSchema provenance variant itself stays in the call crate (alkcall ADR-027 records the provenance-side decision: FromJsonSchema is a handler-bearing leaf in alkcall's OperationProvenance enum).

A concrete use case now forces the decision: composing a non-standard, non-OpenAPI, basic REST endpoint that does not have a full OpenAPI document. The endpoint has a method, a URL, an input/output JSON Schema, and an auth scheme — but no paths object, no operationId, no components. from_openapi requires an OpenAPI document; this endpoint doesn't have one. The gap is: register a single HTTP endpoint as a call-protocol operation, one at a time, with the caller supplying the schema directly.

Decision

from_jsonschema becomes an HTTP-backed single-endpoint adapter in alkhttp, functionally similar to from_openapi but registering one endpoint at a time instead of parsing a full OpenAPI document:

  1. The adapter implementation lives in alkhttp (src/adapters/from_jsonschema.rs). The forwarding handler uses the same reqwest-backed SharedHttpClient and the same no-env-vars credential injection as from_openapi. The adapter implements OperationAdapter (the trait from alkcall, ADR-017 §5 — unchanged).

  2. Give it a real forwarding handler. A from_jsonschema-imported operation is a leaf with a reqwest forwarding handler, identical in shape to a from_openapi-imported operation — it builds an HTTP request from the input (path/query/body split per a path template), injects credentials from context.capabilities, sends via the shared HTTP client, and parses the response (JSON, text, or binary — same content-type branching as from_openapi). For a Sub op type with text/event-stream response, it registers a StreamingHandler (ADR-049), same as from_openapi.

  3. Single-endpoint registration. The caller supplies:

    • An OperationSpec (name, op type, input/output JSON Schema, error_schemas, access_control, visibility).
    • An HttpServiceConfig (base URL, auth scheme, default headers — the same config type from_openapi uses).
    • A path template + HTTP method (the one endpoint).

    The adapter builds one HandlerRegistration with FromJsonSchema provenance and a real forwarding handler. The caller registers it in the OperationRegistry. This is the "one endpoint at a time" shape: no paths object to iterate, no operationId to normalize.

  4. FromJsonSchema provenance stays in alkcall (in the OperationProvenance enum, registration.rs). The provenance type lives where the registry types live; only the adapter implementation moved. FromJsonSchema is now a leaf provenance — it has a handler (a reqwest forwarding handler), same trust model as FromOpenAPI (HTTP endpoint trusted; handler is a forwarding stub).

  5. Remove the "schema-only, no handler" concept. The placeholder handler and the "schema-only ops are Internal, so dispatch should never reach them" rationale are removed. An op registered with FromJsonSchema provenance is a real, callable, HTTP-forwarding operation — Internal by default (adapter-registered ops are composition material, ADR-015), but it actually forwards if invoked.

    The schema-validation-without-a-handler use case (type-checking a composition plan, building a UI) does not require a HandlerRegistration at all. That use case is served by consuming the OperationSpec directly (the spec already carries the input/ output JSON Schemas); no adapter, no registry entry, no handler is needed. If a future use case requires registering a schema-only op for discovery purposes, that is a separate feature and would warrant its own ADR — it is not what from_jsonschema is.

Relationship to from_openapi

from_openapi from_jsonschema
Input A full OpenAPI 3.x document (JSON or YAML) A single endpoint: OperationSpec + HttpServiceConfig + path template + method
Granularity One HandlerRegistration per (path, method) in the doc One HandlerRegistration per call
Schema source Parsed from the OpenAPI doc (parameters, request body, responses) Supplied directly by the caller
Handler reqwest forwarding handler (shared HTTP client) Same reqwest forwarding handler
Provenance FromOpenAPI FromJsonSchema
Location alkhttp alkhttp
Use case Standard OpenAPI APIs (GitHub, OpenAI, Anthropic) Non-standard, non-OpenAPI, or basic REST endpoints without a full spec

The two adapters share the forwarding-handler implementation, the credential injection path, the error-fidelity rule (HTTP_<status> prefix, ADR-023), and the no-env-vars invariant (ADR-014). The difference is purely the input shape: a full document vs. a single endpoint.

Consequences

Positive:

  • from_jsonschema actually works — it has a real handler, not a placeholder. A concrete use case (non-standard REST endpoints) is served.
  • The adapter location is consistent: all HTTP-backed adapters (from_openapi, from_mcp, from_jsonschema) live in the HTTP crate (alkhttp), where reqwest is. The call crate (alkcall) stays lean.
  • The "schema-only, no handler" trap is removed. An op in the registry is always callable.
  • FromJsonSchema provenance becomes a real leaf, consistent with FromOpenAPI/FromMCP/FromCall.

Negative:

  • The schema-validation-without-a-handler use case (the original stated purpose) is no longer served by from_jsonschema. That use case is served by consuming OperationSpec directly, but any code that relied on the placeholder handler returning NOT_FOUND breaks. The only existing consumer was the call crate's own tests; no downstream consumer depended on this — the placeholder was a trap, not a contract.
  • The call crate loses a public export (from_jsonschema, FromJsonSchema the adapter struct). The FromJsonSchema provenance variant stays; the adapter struct moves. Downstream consumers that referenced the adapter (none currently) would need to use alkhttp's re-export.

Neutral:

  • FromJsonSchema provenance is now a leaf (handler-bearing), not a "no handler" provenance. The ADR-022 table row updates: it can compose? No. Has composition authority? No. Default visibility? Internal. Trust model? HTTP endpoint trusted; handler is a forwarding stub. This aligns with the other leaves. ADR-017 §5 and ADR-022's provenance table/enum-doc are amended (2026-07-09) to point here — the supersession is recorded in the superseded ADRs, not only in this one.

References

  • Supersedes the from_jsonschema clause of ADR-017 §5 ("FromJsonSchema — imports from a JSON Schema definition (schema-only, no handler)") and the operational spec in the call crate's client-and-adapters.md §"from_jsonschema" (alknet mono-repo: docs/architecture/crates/call/client-and-adapters.md).
  • Supersedes the FromJsonSchema row of ADR-022 (the "no handler — schema only" framing).
  • Aligns with the adapter location principle in ADR-017 §5 and the call crate's client-and-adapters.md §"Adapter Location Map": HTTP-backed adapters live in the HTTP crate (alkhttp).
  • Reuses the forwarding handler, credential injection, error fidelity (HTTP_<status> prefix, ADR-023), streaming shape (ADR-049), and no-env-vars invariant (ADR-014) established by from_openapi.
  • Reuses HttpServiceConfig and SharedHttpClient from from_openapi (in alkhttp).
  • alkcall ADR-027 — the decision record in the call crate (from_jsonschema as an HTTP-backed adapter; FromJsonSchema provenance is a handler-bearing leaf in alkcall's OperationProvenance).

Port notes

  • History accuracy (kept per the porting instruction): the move this ADR records happened in two hops. Originally the adapter was specified in the call crate (alknet-call) as a schema-only placeholder; alknet ADR-066 moved it to alknet-http (the HTTP crate of the alknet mono-repo); with the crate extraction, alknet-http is now alkhttp. The title, §Decision 1, the location table, and the Consequences now name alkhttp as the home. The original title said "in alknet-http"; the ported title says "in alkhttp" — same crate, current name.
  • Provenance location (per alkcall ADR-027): FromJsonSchema provenance lives in alkcall (the call crate's OperationProvenance enum) — the provenance is a handler-bearing leaf in alkcall; only the adapter implementation lives in alkhttp. The original already said this; the port names the current crates and adds the alkcall ADR-027 citation to the References.
  • Renames: "alknet-http" → alkhttp; "alknet-call" → "the call crate (alkcall)"; the source-file path crates/alknet-http/src/adapters/from_jsonschema.rssrc/adapters/from_jsonschema.rs (this crate's layout); "the HTTP crate" phrasing retained where the original used it generically.
  • SubscriptionSub (alkcall rename; alkcall ADR-046 added OperationType::Pub — producer→consumer streaming via call.published, HandlerKind::Sink — which does not affect this adapter: from_jsonschema detects SSE responses as Sub and registers HandlerKind::Stream, same as from_openapi).
  • Cross-reference remappings (verified alknet→alkcall ADR mapping): alknet ADR-017 (adapter contract) → alkcall ADR-022; alknet ADR-022 (handler registration) → alkcall ADR-018; alknet ADR-023 (error schemas) → alkcall ADR-016; alknet ADR-049 (streaming handler) → alkcall ADR-021; alknet ADR-014 (secret material flow) → alkcall ADR-010. ADR-014/017/022/023/049 are ported to this crate under the same numbers and linked.
  • The client-and-adapters.md references became textual "the call crate's client-and-adapters.md" references with the alknet mono-repo path noted (the document lives in alkcall's docs/architecture/).
  • Status line: the superseded clauses (ADR-017 §5, ADR-022's FromJsonSchema row) are cited as this crate's ported ADRs — the supersession text is unchanged from the alknet original.
  • No decision content changed — the real-forwarding-handler requirement, the single-endpoint registration shape, the provenance location, the removal of the schema-only concept, the comparison table, and the neutral/positive/negative consequences are verbatim from the alknet ADR modulo the corrections logged above.