All notable changes to layr8. Format loosely follows Keep a Changelog; versioning follows SemVer.
This file starts here. Earlier releases (through 0.2.8) are recorded only in git history.
[Unreleased]
0.2.9 - 2026-08-10
Added
Verifiable Grants are attached to outbound messages — automatically, on every send path (
send/3,request/3, and a handler's reply). The cloud-node requires a grant for anything its policy does not allow outright, and nothing in this SDK attached one: an agent that connected directly sent nothing and was denied with "no grant covers this call", a message that reads as "your grant is misconfigured" when the truth is "no credential was ever put on the wire".Layr8.Walletreads the holder's credentials from the node, caches them for:grant_cache_ms(default 60s) and selects the covering set with a mirror of helix'sstructure_v2.rego. Caller-supplied attachments are never displaced, and a wallet failure never blocks the send.New config:
:attach_grants(defaulttrue, envLAYR8_ATTACH_GRANTS),:grant_cache_ms,:grant_read_timeout_ms,:on_grant_miss. New API:Layr8.Client.refresh_grants/2.Contract:
contracts/sender-cn-vg-attachment.md.:on_grant_miss— told when the node denied a message that went out with nothing attached, when the covering set had to be capped at 16, or when the grants could not be read at all. It deliberately stays quiet on "nothing covered this message" alone: most traffic (discovery, trust-ping, problem reports) needs no grant.Layr8.Mcp— MCP over DIDComm.Layr8.Client.mcp/2registers the protocol subscription and returns a binding;Layr8.Mcp.peer/2yields a caller withinitialize/3,list_tools/2andcall_tool/4. It handles thetools/call→#{base}/tools-calltype mapping, the JSON-RPC envelope and unwrappingresult. Must be called beforeconnect/1, likehandle/3. Contract:contracts/mcp-over-didcomm.md.Layr8.Client.request_result/3—request/3without the raises, for callers that route on failure rather than rescuing it.Layr8.Mcpuses it, which is why its whole surface is tagged tuples.:rest_timeout_ms(default 30s, envLAYR8_REST_TIMEOUT_MS) — a deadline on every credential and presentation call, not just the grant read.Reqhas none of its own, so a node that accepted the connection and went quiet left those calls hanging.0disables it.
Changed
Every send now performs a credential read against the node before the message goes out (once per
:grant_cache_msper DID; failures are cached for a shorter window so a misconfigured API key is not a per-message round trip). A node that cannot serve/api/v1/credentialsdegrades to sending unattached — the previous behaviour — and:on_grant_missreports it. Setattach_grants: falseto opt out entirely.Layr8.RESTno longer retries.Reqretries safe requests by default with a 1s/2s/4s backoff, and becausereceive_timeoutis per attempt, that silently multiplied every deadline by four: measured, a 2s grant read against a node that accepts the connection and goes quiet took 7.6 seconds to return. A deadline exists here so a hung node cannot stall the sends queued behind it, soretry: falsenow goes wherever a deadline goes. Retrying is the caller's decision, with its own knowledge of whether the call is worth repeating.Layr8.REST.new/2,get/2andpost/3gained optional trailing arguments; the existing arities are unchanged.
Fixed
Layr8.Attachment's documentation pointed at the wrong field. It said to putapplication/vc+jwtinformatand to carry credentials indata.base64.media_typeis the ONLY field the node's credential extractor filters on, by exact string equality, and it drops everything else before looking at the data — producing a denial byte-for-byte identical to the one for attaching nothing. Following those docs attached nothing at all. The same correction is onLayr8.Presentations.sign_presentation/3, which is not the authorization path either.