# Changelog
Notable changes to the published crates. Generated from conventional commits by
[git-cliff](https://git-cliff.org) when a release is cut — do not edit by hand.
## [0.11.2](https://github.com/OpenVTC/verifiable-trust-infrastructure/compare/vta-cli-common-v0.11.1...vta-cli-common-v0.11.2) — 2026-08-20
### Fixed
- **tee**: Bootstrap 410 and vsock enotconn ([#1003](https://github.com/OpenVTC/verifiable-trust-infrastructure/pull/1003))
* fix(tee): retry transient ENOTCONN on first vsock config-overlay read
tokio-vsock can report a stream connected just before Nitro finishes the
nonblocking handshake, so the very first read on a fresh vsock:5800
connection to the parent config server can return ENOTCONN even though
the parent is listening and ready. Retry only that specific transient
error kind with a short delay; any other I/O error still fails closed
immediately, and the existing overall READ_TIMEOUT deadline still
bounds the whole fetch.
Adds positive (retries ENOTCONN then succeeds) and negative (does not
retry PermissionDenied) unit tests against the inner read loop.
## [0.11.1](https://github.com/OpenVTC/verifiable-trust-infrastructure/compare/vta-cli-common-v0.11.0...vta-cli-common-v0.11.1) — 2026-08-18
## [0.11.0](https://github.com/OpenVTC/verifiable-trust-infrastructure/compare/vta-cli-common-v0.10.33...vta-cli-common-v0.11.0) — 2026-08-17
### Added
- **vta-keys**: Add non-extractable internal signing keys ([#995](https://github.com/OpenVTC/verifiable-trust-infrastructure/pull/995))
An ordinary VTA key is BIP-32 derived, so anyone holding the 24-word mnemonic
can reconstruct it offline. That is what makes the VTA recoverable, and equally
what makes "the operator cannot obtain this key" false — the second limb of what
eIDAS calls sole control.
An internal key is generated from the system CSPRNG, has no derivation path, and
is never returned by any surface. The VTA acts only as a signing oracle for it.
Deliberately not a flag on the imported-key path. That path wraps its secrets
under a KEK derived from the master seed (derive_kek(seed, salt)), so a
non-extractable flag on it would be decorative: the boundary it claims to
enforce has already been walked around. Internal keys get their own keyspace,
INTERNAL_KEYS, with no seed involvement at any point, and that keyspace is in
EXCLUDED_FROM_BACKUP by design — a backup carrying it would be an export of keys
the VTA promises never to export, and restoring it elsewhere would clone a
signer.
Refused for did:webvh log entries, enforced in code rather than left to
guidance. WebVH is append-only and each entry is authorised by the update key
the previous entry named; an unrecoverable update key means that if storage is
lost the DID can never be updated again by anyone, permanently, and every
integration pinned to it is stranded. Credentials can be re-issued, an
append-only identity log cannot. Internal keys remain fine as a signing
verificationMethod inside a published document, where loss costs the ability to
produce new signatures rather than control of the identity.
The export refusal is not a permission check — admin is not a bypass, because
the value of the origin is that no caller holds this power. There are two
refusals (an early return and an in-match arm); removing either leaves the other,
and removing both does not compile, since the match over KeyOrigin becomes
non-exhaustive. An export path cannot silently reopen.
Operator surfaces carry the cost prominently: `pnm keys create --internal`
prints what is lost and requires the operator to type a confirmation phrase
rather than mash y, the response repeats the warning, and docs/02-vta/
internal-keys.md covers when to use one, what actually protects it (enclave
measurement + KMS, not a mnemonic), and the two things that genuinely destroy
it.
## [0.10.33](https://github.com/OpenVTC/verifiable-trust-infrastructure/compare/vta-cli-common-v0.10.32...vta-cli-common-v0.10.33) — 2026-08-16
## [0.10.32](https://github.com/OpenVTC/verifiable-trust-infrastructure/compare/vta-cli-common-v0.10.31...vta-cli-common-v0.10.32) — 2026-08-16
## [0.10.31](https://github.com/OpenVTC/verifiable-trust-infrastructure/compare/vta-cli-common-v0.10.30...vta-cli-common-v0.10.31) — 2026-08-14
### Fixed
- **cli**: Make `dids list` show the DID, and plan errors say what failed ([#967](https://github.com/OpenVTC/verifiable-trust-infrastructure/pull/967))
Two unrelated fixes to the same delegated-update path, found chasing a
`webvh/dids/update` that failed after its consent gate passed.
**`pnm dids list` rendered a wide name beside an unreadable DID.** A
ratatui `Table` lays out exactly as many columns as it has width
constraints — a widths list shorter than the row is not padded, it
truncates. `dids list` builds its header and its rows with a conditional
Name column but built the widths without one, so every width landed a
column to the left: Name inherited the DID's flexing `Min`, the DID
inherited Context's fixed 16 (`did:webvh:Qm0M8Cr`, cut mid-SCID) and
`Created` fell off the right-hand end. The servers table above it had the
same shape of bug from the other direction — its widths were written
against a different column order, and their own comments still said so.
Header and widths are now returned together from `did_list_columns`, so
the two cannot drift, and the DID column starts at 46 columns: `shorten_did`
abbreviates only the SCID and keeps host and path in full, which is what
makes the value copyable.
**Every webvh dry-run failure became `internalError`.** The planner runs
on the consent path and only there, so that flattening applied to exactly
the report an approver-gated update produces: a DID the VTA does not hold,
a context the requester cannot act in, and a genuine signing bug all
arrived as one opaque internal error — while the *ungated* execution of
the very same task answered `taskFailed: did not found: …`. Turning
consent on made the diagnosis worse than leaving it off.
Dry-run failures now route through the existing
`From<UpdateDidWebvhError> for AppError`, so plan and execute answer with
the same variant for the same cause, with `webvh update dry-run:` framing
the message. The Forbidden-collapses-to-NotFound rule that stops a
dry-run being used to probe for DIDs in unseen contexts is preserved, and
pinned by a test.
## [0.10.30](https://github.com/OpenVTC/verifiable-trust-infrastructure/compare/vta-cli-common-v0.10.29...vta-cli-common-v0.10.30) — 2026-08-12
### Added
- **did-webvh**: Let a minted DID advertise TSP at the VTA's mediator ([#959](https://github.com/OpenVTC/verifiable-trust-infrastructure/pull/959))
A VTA-minted DID could never advertise TSP, whatever the VTA's own config
said. `add_mediator_service` publishes the VTA's mediator as a
`DIDCommMessaging` service and nothing else, so a caller wanting `#tsp`
had to hand-build the service entry and pass it through
`additional_services` — which means knowing the mediator DID, the one
thing `add_mediator_service` exists so a caller does not have to know.
Nobody did, so every persona-shaped identity is DIDComm-only by
construction, and the both-ends transport rule can never resolve to TSP
for one. TSP could be enabled end to end and the intersection would still
be DIDComm.
Surfaced by OpenVTC #211, where a join failed at the mediator and the
applicant persona's document turned out to carry exactly one service
entry.
Adds `add_tsp_service` to the create-DID wire, honoured by
`with_tsp_service` in `did_webvh/document.rs`. The entry points at the
same mediator the DIDComm entry names — TSP advertises a mediator DID,
not a transport URL (D8) — using the fragment and type the setup path and
the runtime `services tsp enable` patcher already emit, so a document
minted here, minted at setup, or patched later are the same shape.
Two gates, neither redundant. The caller's flag is opt-in and
deliberately not implied by `add_mediator_service`: a DID advertising a
transport its holder cannot decode is unreachable over that transport,
and only the caller knows whether the client behind the DID reads TSP
frames. Ours is `[services] tsp` plus a configured mediator: a VTA whose
own stack does not run TSP must not mint documents claiming it does,
which is the failure this prevents rather than spreads. A caller-supplied
`TSPTransport` entry wins over the injected one — matched on the service
`type`, never the `#id` fragment.
Additive on the wire in both directions: `skip_serializing_if` on the
request and `Option` on the body, so an unset field serialises exactly as
before and a VTA that predates it ignores the key.
## [0.10.29](https://github.com/OpenVTC/verifiable-trust-infrastructure/compare/vta-cli-common-v0.10.28...vta-cli-common-v0.10.29) — 2026-08-12
### Fixed
- **vault**: Send entryId on vault release, from both the CLI and the MCP bridge ([#948](https://github.com/OpenVTC/verifiable-trust-infrastructure/pull/948))
* fix(vault): use entryId instead of id in vault release payload
cmd_vault_release was constructing the vault/release/0.1 Trust Task
payload with key `id`, which fails schema validation. The schema
requires `entryId` (matching VaultReleaseBody's camelCase
serialisation on the server side).