trust-tasks-https 0.12.0

HTTPS transport binding for the Trust Tasks framework — typed client + axum-based server with bearer-auth identity, suitable for demos, mockups, and end-to-end testing.
# Changelog

All notable changes to `trust-tasks-https` are documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
this crate tracks `trust-tasks-rs`'s `MAJOR.MINOR`.

## [0.12.0] - 2026-08-26

### Changed

- **`trust-tasks-rs` requirement moved to `0.12`.** That crate's `0.12.0` makes
  the duplicate-execution record of SPEC §7.2 item 11 and the freshness bound of
  item 13 part of `consume_inbound`. This crate re-exports its types, so the
  leading component moves with it.

  This server does **not** yet get the guard: its pipeline runs its own §7.2
  checks and never reaches `consume_inbound`, so a `ReplayGuard` still has to be
  threaded through the handler path. Until it is, an HTTPS deployment has no
  duplicate-execution defence.

## [0.11.1] - 2026-08-26

### Changed

- **The two fallback error documents now carry the `trust-task-error` version
  the rest of the stack emits.** `synthesise_error` and
  `suppressed_error_response` spelled `trust-task-error/0.2` out for
  themselves, while `trust-tasks-rs` and the TypeScript runtime both emitted
  `0.5` — so the version a producer saw depended on which branch of the
  rejection path its request hit, and neither README described either number.
  Both now call `trust_tasks_rs::trust_task_error_type_uri()`, which
  `trust-tasks-rs` 0.11.18 made public as the single source of truth.

  `0.5` is a forward-minor move from `0.2`; per SPEC §5.2 a `0.2` consumer
  SHOULD accept it. Every *other* error response this server emits already
  carried `0.5`, because they are built by `TrustTask::reject_with` in
  `trust-tasks-rs` — these two branches were the only ones that disagreed.

### Fixed

- `proof_invalid_when_verifier_rejects` asserted that the verifier's own error
  description reaches the wire. It does not any more, and should not have:
  SPEC §10.4 makes that a resolver-reachability and DID-document oracle for a
  sender who is by construction unauthenticated. The test is renamed
  `proof_invalid_wire_message_withholds_the_verifier_description` and now
  asserts both halves — the constant on the wire, and the description still
  present on `RejectReason`'s `Display` for the operator's log. The sanitising
  itself is in `trust-tasks-rs` 0.11.18.

## [0.11.0] - 2026-08-26

Security release. **Every item under "Changed" alters the behaviour of an
existing deployment**, most of them by refusing traffic that used to be
accepted. Nothing on the wire moved; read the first entry before upgrading.

### Changed

- **BREAKING (behaviour, not API): unattributable documents are now rejected.**
  A new `HttpsServerBuilder::require_attribution` option **defaults to `true`**.
  A document arriving with neither a transport-authenticated peer nor a `proof`
  is refused with `proofRequired` before any handler runs.

  Previously `build()` installed a `BearerAuth` with an empty token map, the
  peer resolved to `None`, and SPEC §4.8.1 then fell back **entirely** to the
  document's in-band `issuer` — an unverified string the sender chose. The
  per-spec `IS_PROOF_REQUIRED` check was the only thing standing behind it, and
  133 of the 344 generated spec modules never set it — including mutating ones
  (`auth/step-up/approve-response/0.2`, `did-management/did/register/0.1`,
  `messaging/admin/add/0.1`). So an unauthenticated, proofless POST claiming
  `"issuer": "did:web:victim"` reached a handler with that string presented as
  the caller's identity. The binding specification says plainly that this
  binding "does not permit `proof` to be omitted"; the runtime had no
  representation of that rule.

  **A deployment that relied on unauthenticated, proofless requests will start
  seeing `proofRequired`/422.** The fix is to authenticate the caller (or have
  it sign). `.require_attribution(false)` restores the old behaviour and is
  documented for local development and tests only — its rustdoc spells out what
  it re-opens.

- **BREAKING: the request pipeline is reordered.** Route lookup, transport auth
  and the attribution gate now run **before** proof verification. Verifying a
  proof resolves its `verificationMethod` DID, which for `did:web` is an
  outbound HTTPS request to a host the *sender* named — and the proof block used
  to run at step 4a, ahead of route lookup at step 5. A stranger could therefore
  make a server built `with_verifier` fetch an arbitrary host by POSTing a
  document whose `type` that server does not even implement, unauthenticated and
  unrated-limited. An unknown type or an unattributable sender is now rejected
  before anything touches the network. Observable consequence: a request that
  was both unroutable *and* carried a bad proof now reports `unsupportedType`
  where it used to report `proofInvalid`.

- **BREAKING: the status table now matches the binding specification's §4.**
  `proofRequired`, `proofInvalid`, `identityMismatch`, `wrongRecipient`,
  `unsupportedType`, `unsupportedVersion` and `expired` all map to **422**;
  they previously mapped to 401, 401, 403, 403, 400, 400 and 400. Any client
  branching on those HTTP statuses needs updating (the framework error code in
  the body is unchanged and remains authoritative). The finer split was itself
  the identity oracle the specification's flat 422 exists to avoid: it told an
  unauthenticated prober, without reading a body, whether its proof or its
  claimed identity was the problem. `malformedRequest`/400,
  `permissionDenied`/403, `idConflict`/409, `unavailable`/503 and
  `internalError`/500 are unchanged.

- **BREAKING: discovery requires an authenticated discoverer by default.**
  `enable_discovery()` / `with_discovery()` built the registry with no auth
  predicate, so any unauthenticated POST got back the server's full route table.
  SPEC §10 says a responder **SHOULD** authenticate the discoverer first. Opt
  back in with the new `HttpsServerBuilder::public_discovery()`.

- **BREAKING: `Content-Type: application/json` is now required**, per the
  binding specification §2; anything else (or an absent header) is refused with
  **415**. `dispatch_handler` took raw `Bytes` and never looked. `text/plain` is
  one of the media types a cross-origin `fetch` or HTML form may send *without*
  a CORS preflight, so any page in a victim's browser could drive this endpoint;
  requiring JSON forces the preflight and the browser refuses on our behalf.

- **BREAKING: `HttpsClient::send` binds each response to its request.** It used
  to return any 2xx body that merely deserialised as `TrustTask<Resp>`. It now
  requires the response's `threadId` to equal the request's `threadId ?? id`,
  its `type` to be the request's `type` with `#response`, its `issuer` to be the
  configured `server_vid` and its `recipient` to be `my_vid` — each with its own
  `ClientError` variant. On an error response, `inResponseTo.id` must name the
  request where the responder populated it (absent under `identityMismatch` per
  SPEC §8.1, which stays acceptable). `ClientError` gained seven variants, which
  is breaking for exhaustive `match`.

- The binding identifier is `https://trusttasks.org/binding/https/0.2`.
  `BINDING_URI`, the crate-level module docs and the README all said `0.1`.

### Added

- `HttpsServerBuilder::allowed_did_methods` — pre-screens the DID method in
  `proof.verificationMethod` before the verifier is called, bounding where
  proof-verification egress can go. Off by default. Depth (resolved-host
  allow-listing, redirect policy, per-resolution timeouts) belongs in the
  resolver behind the verifier, not at this boundary.
- `HttpsServerBuilder::request_timeout` and
  `HttpsServerBuilder::max_concurrent_requests`, with the constants
  `DEFAULT_REQUEST_TIMEOUT` (30s) and `DEFAULT_MAX_CONCURRENT_REQUESTS` (512).
  `into_router` now applies a tower `TimeoutLayer` and `ConcurrencyLimitLayer`;
  `serve()` was previously a bare `axum::serve` with neither, so a stalled
  request held a connection and a task open indefinitely (slowloris). An expired
  request answers `408`.
- `MAX_BODY_BYTES` is now public and exported. A caller replacing the router's
  `DefaultBodyLimit` layer wants to know what it is replacing.
- `HttpsClientBuilder::with_response_verifier` — optional verification of the
  `proof` on response documents. Configuring it makes a signed response
  mandatory: a proofless response is rejected rather than silently downgraded.

## [0.8.0] - 2026-08-16

### Changed

- Requires `trust-tasks-rs` 0.8, which adds the `cancelled` standard error code
  (framework 0.4, SPEC §8.3) and the `trust-task-control/0.1` payload types.
  Additive on the Rust side — `StandardCode` has been `#[non_exhaustive]` since
  0.7.0 — so this crate needed no source change.
- `cancelled` maps to **HTTP 422**, the same bucket as `taskFailed`: a
  deliberate stop is neither a server fault nor a malformed request.

## [0.7.0] - 2026-08-15

### Changed

- **BREAKING.** Requires `trust-tasks-rs` 0.7, whose `StandardCode` is now
  `#[non_exhaustive]` and carries the new `idConflict` code (framework 0.4,
  SPEC §8.3). Any `match` over `StandardCode` that this crate's types reach
  needs a wildcard arm.
- `idConflict` maps to **HTTP 409 Conflict**. The status table gained a
  wildcard arm sending any future unmapped code to 500, so a framework
  revision no longer fails to compile here.

## [0.6.0] - 2026-08-10

### Changed

- **BREAKING.** Requires `trust-tasks-rs` 0.6, which narrows `DigestMultibase`
  to the multibase headers CID 1.0 requires. The core types cross this crate's
  public API, so a graph mixing 0.5 with this crate will not type-check. No API
  of this crate changed on its own account.

## [0.5.0] - 2026-08-09

### Changed

- **BREAKING.** Requires `trust-tasks-rs` 0.5. That release adds a field to
  `TrustTask<P>` for the framework 0.4 `ceremony` member, and the core types
  cross this crate's public API, so a dependency graph mixing 0.4 with this
  crate will not type-check. No API of this crate changed on its own account.

## [0.4.0] - 2026-08-09

### Changed

- **BREAKING.** Requires `trust-tasks-rs` 0.4. That release changes digest
  payload members from `String` to the validating `DigestMultibase` newtype, and
  the core types cross this crate's public API, so a dependency graph mixing
  `trust-tasks-rs` 0.3 with this crate will not type-check. No API of this crate
  changed on its own account.

## [0.2.2] — 2026-07-17

### Fixed

- `DEFAULT_TIMEOUT` / `DEFAULT_CONNECT_TIMEOUT` (added in 0.2.1) are now
  re-exported from the crate root as intended; previously they were
  documented as public API but unreachable, which also broke the
  workspace doc build under `-D warnings`.

## [0.2.1] — 2026-07-16

### Added

- `HttpsClientBuilder::timeout(Duration)` and
  `HttpsClientBuilder::connect_timeout(Duration)`, plus the
  `DEFAULT_TIMEOUT` (30s) / `DEFAULT_CONNECT_TIMEOUT` (10s) constants.

### Changed

- **The client's `reqwest::Client` now carries finite timeouts by
  default.** Previously it was built with no timeout at all, so a peer
  that accepted the connection and never answered would hang the caller
  forever. An exchange that legitimately needs longer than 30s can raise
  the limits via the new builder methods. Behavioral change: calls that
  previously hung indefinitely now fail with a `ClientError::Http`
  timeout error.

## [0.1.2] — 2026-05-27

### Changed

- Track `trust-tasks-rs` 0.1.2. No public API changes in this crate; the
  bump exists so consumers can `cargo update -p trust-tasks-https` and
  pick up the new spec families (`did-management/*`, `webvh/*`,
  `vault/*`, `device/*`, `policy/*`, `provision/integration`, etc.) over
  the HTTPS transport without further dependency surgery.

## [0.1.1] — 2026-05-19

### Added

- `HttpsServerBuilder::with_verifier(verifier)` — plug in any
  `trust_tasks_rs::ProofVerifier` implementation. When configured, the
  server verifies the `proof` member of every proof-bearing inbound
  document and rejects `proof_invalid` on failure; when absent, the
  server rejects proof-bearing documents with `malformed_request` as
  before. Stored internally as `Arc<DynProofVerifier>` for object-safe
  dispatch. This lets `acl/grant` / `acl/revoke` / `acl/change-role`
  (all `proofRequirement: REQUIRED`) flow end-to-end on the binding
  once a verifier is configured — without one, `IS_PROOF_REQUIRED`
  enforcement still fires per the security fix above.
- `RequestContext.resolved: ResolvedParties` — handlers can now read
  the SPEC §4.8.1-resolved issuer/recipient pair directly instead of
  re-running `TransportHandler::resolve_parties` to re-derive what the
  dispatch pipeline already computed. Matches the ergonomic improvement
  in `trust-tasks-rs` `consume_inbound`'s handler signature.

### Fixed — security

- The server previously accepted documents carrying a `proof` member
  without verifying it (the binding has no in-band verifier). A producer
  that signed its document and saw a 200 had no way to learn that the
  signature was never checked. The server now rejects proof-bearing
  documents with `malformed_request` at the framework-checks stage,
  matching the same rule `consume_inbound` now applies under
  `ProofPolicy::RejectIfPresent`. The wire-exposed reason is the
  framework-shared `PROOF_NOT_ACCEPTED_BY_POLICY` constant — naming
  the server's configuration would let an unauthenticated probe
  fingerprint verifier coverage across a fleet.
- The server previously accepted *proofless* documents on specs whose
  front matter declares `proofRequirement.requirement: REQUIRED`  silently violating SPEC §7.2 item 7. The dispatch closure now
  consults `Payload::IS_PROOF_REQUIRED` after downcast and rejects with
  `proof_required` when the spec requires a proof and none is present,
  regardless of whether the binding is configured to verify.
  Combined with the verifier plug-in point added under "Added" below,
  REQUIRED specs (e.g. `acl/grant`, `acl/revoke`, `acl/change-role`)
  now flow end-to-end on the binding when a verifier is wired in via
  `HttpsServerBuilder::with_verifier`; without a verifier they are
  correctly refused with `proof_required` (proofless) or
  `malformed_request` (proof-bearing, the policy rejection above).

## [0.1.0] — initial pre-release, tracks `trust-tasks-rs` 0.1, `SPEC.md` 0.1

### Added

- HTTPS transport binding for the Trust Tasks framework (SPEC §9).
  Binding URI: `https://trusttasks.org/binding/https/0.1`.
- `HttpsServer` — axum-based, single `POST /trust-tasks` endpoint.
  Builder API: `HttpsServer::builder().local_vid(...).with_auth(...).on::<P, Resp, _>(handler).build()`.
  Runs the full SPEC §7.2 pipeline per request: `resolve_parties`
  (§4.8.1 in-band-wins), `validate_basic` (items 4 + 5),
  `enforce_audience_binding` (item 8 / §4.8.2), dispatch by canonical
  Type URI (§4.4.1 item 1), then the user handler. Success ⇒
  `respond_with`; failure ⇒ `TransportHandler::reject` (§8.1 routing).
- `HttpsServer::with_discovery(registry)` and
  `HttpsServer::enable_discovery()` — one-line wiring of
  `trust-task-discovery/0.1` on the server. `enable_discovery()`
  snapshots every Type URI registered via `.on(...)` (plus discovery
  itself) and serves the matching subset on each query.
- `HttpsClient` — reqwest-based typed `send::<Req, Resp>` with bearer
  auth, automatic in-band identity defaulting, and `ClientError` that
  distinguishes transport-level failures, framework
  `trust-task-error/0.1` documents, and untyped non-2xx fallbacks.
- `HttpsHandler``TransportHandler` impl that maps the bearer-
  authenticated sender into the framework's transport peer.
- `BearerAuth` — minimal `HashMap<token, VID>` `Auth` implementation
  for demos and tests; production deployments plug in their own
  `Auth` impl.
- `status_for_code(code) -> u16` — informative HTTP status mapping
  from `StandardCode` (400 / 401 / 403 / 422 / 500 / 503).
- `examples/server_demo.rs` + `examples/client_demo.rs` — runnable
  end-to-end demo on `localhost:3000`.
- `tests/end_to_end.rs` — full HTTP loop covering happy path,
  identity-mismatch routing + sanitised wire message,
  unsupported-type, spec-handler rejection, plus discovery
  advertisement and pattern-filtered discovery.

### Cargo features

- `client` (default) — `HttpsClient` + `reqwest`.
- `server` (default) — `HttpsServer` + `axum` + `tokio` + `tower`.

[0.1.1]: https://github.com/trustoverip/dtgwg-trust-tasks-tf/releases/tag/trust-tasks-https-v0.1.1
[0.1.0]: https://github.com/trustoverip/dtgwg-trust-tasks-tf/releases/tag/v0.1.0