# 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