fix(core): recognise trust-task-error at any 0.x, not just 0.1 and 0.2 - #115
Merged
Conversation
`isTrustTaskErrorType` enumerated two framework error-document versions.
`trust-tasks-rs` has emitted `trust-task-error/0.3` since its 0.3 release —
it carries the §8.2 `inResponseTo` member, and `0.2`'s payload schema is
`additionalProperties: false`, so the document cannot claim to be `0.2`. The
VTA moved to 0.4 and every rejection it sends has been unrecognised here since.
Unrecognised does not mean "reported with the wrong wording". An error document
this predicate rejects is not an error at all: `parseTrustTaskReply` falls
through to the success branch and returns the `ErrorPayload` as the operation's
*result*. So a refused task arrived at the calling page as a completed one. A
`webvh/dids/update` the VTA had refused ("did not found: SCID … not found",
returned 422) rendered "your agent signed and published the update" in the
hosting UI, and nothing was published.
A `requireConsent` refusal is the same document, which is the more serious half.
`requestTask` recognises consent inside a *thrown* `VtaClientError`; with no
throw it resolved as `accepted` instead, so the cross-device match code never
rendered and the approver was never asked. That is silent on both sides — the
requester sees success, the approver sees nothing — and it would have been the
first thing hit by the next VTA to configure an approver set.
Match the framework slug at any `0.x` minor instead, which is what SPEC.md §5.2's
forward-minor rule asks of a consumer, so the next minor cannot break this the
same way. `1.x` is deliberately excluded: a major bump is where the payload shape
may genuinely change, and that should come back through a code change rather than
be decoded under 0.x assumptions. All four consumers — the REST channel,
`parseTrustTaskReply`, the approver's decision-outcome reader and push-gateway
registration — already routed through this one predicate, so they are all fixed.
Why no test caught it: every test that built an error document hard-coded
`/0.1`, a version no VTA emits. `rejecting()` now defaults to what actually
ships and the consent tests run across all three versions, so the suite
exercises the wire it will meet. Reverting the predicate fails nine tests,
including the pre-existing consent ones.
Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
stormer78
added a commit
to affinidi/affinidi-webvh-service
that referenced
this pull request
Aug 14, 2026
Every routed rejection goes out through `TrustTask::reject_with`, which stamps whatever version `trust-tasks-rs` emits — `trust-task-error/0.3` since the framework's own 0.3 release, for the §8.2 `inResponseTo` member that `0.2`'s `additionalProperties: false` payload schema cannot admit. But four paths in this service are **unrouted**: the body never parsed into a Trust Task, so there is no request document to reject from and the Type URI has to be written out by hand. All four wrote `0.1`: * `routes::trust_tasks::error_type_uri` * `routes::auth::trust_task_malformed` * `messaging::body_parse_error` * the TSP transport's parse-failure reply So which version a caller saw depended on whether its request happened to parse. That is a trap for exactly the consumer that pins a version, and it is not hypothetical — the trap sprang twice this week. A client enumerating `0.1` and `0.2` decodes a `0.3` rejection as a **success**, because an unrecognised error document falls through to the success branch and its payload is returned as the operation's result (#160 here, OpenVTC/vta-browser-plugin#115 in the wallet). Emitting two versions is worse than emitting the wrong one: whichever a consumer pins, it is right half the time, and the half it gets wrong is silent. `trust-tasks-rs` keeps `trust_task_error_type_uri()` `pub(crate)`, so the value cannot be read back from the framework. It is now named once for the workspace, in `did_hosting_common::server::trust_tasks::framework_error_type_uri`, and a test compares it against the Type URI a real `reject_with` produces. A framework bump now fails that test rather than re-splitting the service into two dialects. Test assertions that hard-coded `0.1` now assert against that function instead of a literal, so they cannot drift away from what the service emits — the same reason the JS suites were changed in #160: a test that exercises a wire nobody speaks is how a version pin survives unnoticed. Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
stormer78
added a commit
to OpenVTC/verifiable-trust-infrastructure
that referenced
this pull request
Aug 14, 2026
…ocument (#973) * fix(trust-tasks): stop emitting two versions of the framework error document Both services routed their rejections through `TrustTask::reject_with`, which stamps whatever version `trust-tasks-rs` emits — `trust-task-error/0.3` since the framework's own 0.3 release. But each service also has one *unrouted* path, for a body that never parsed into a Trust Task at all, and with no request document to reject from it had to write the Type URI out by hand. Both wrote `0.1`. So a single service spoke two dialects, distinguished only by whether the request happened to parse. That is a trap for exactly the consumer that pins a version, and it is not hypothetical: a client enumerating `0.1`/`0.2` read every `0.3` rejection as a **success**, because an unrecognised error document falls through to the success branch and its payload is returned as the operation's result (OpenVTC/vta-browser-plugin#115, affinidi/affinidi-webvh-service#160). The version a service emits is wire contract; emitting two is worse than emitting the wrong one, because whichever a consumer pins is right half the time. `trust-tasks-rs` keeps `trust_task_error_type_uri()` `pub(crate)`, so the value cannot be read from the framework. Each service now names it once, in `framework_error_type_uri()` beside the unrouted builder, and a test compares that against the Type URI a real `reject_with` produces. A framework bump now fails a test instead of silently re-splitting the service in two. A second test asserts the bytes on the wire carry it, not just the value we compute. Test fixtures that stood in for a peer's rejection were built at `0.1` — a version no peer on trust-tasks-rs 0.4 sends. They now use what a peer actually emits. Those assertions pass either way (the matchers key on the slug, which is the right way to match), but a suite that exercises a wire nobody speaks is how the client-side version pin survived this long unnoticed. Left alone deliberately: `vtc-service::messaging`'s `.unwrap_or(…/0.1)` default, which labels an inbound document that carries no `type` at all. It is not an emitted document, and every consumer of that label matches on the slug. Signed-off-by: Glenn Gore <glenn.g@affinidi.com> * fix(vtc): name one spelling of the framework error URI, as the census requires The census (`vtc-service/tests/trust_task_manifest.rs`) failed on the previous commit, and it was right twice. **One URI per family.** `UNPUBLISHED_CANONICAL_OK` pins `trust-task-error/` at exactly one URI. The previous commit moved the emitted version to `0.3` but left a `0.1` literal in `messaging.rs` — the `.unwrap_or(…)` that labels an inbound reply carrying no `type` at all. That was a deliberate call ("not an emitted document, every consumer matches on the slug") and it was the wrong one: two spellings in the crate is exactly the condition this whole change set exists to remove, whether or not each one reaches the wire. The default now goes through `framework_error_type_uri`, so the crate holds a single literal. **A test probe is a binding.** The new test typed its throwaway request document `spec/vtc/join/request/0.1`, which the registry does not publish. The census scans source literals including test code, because a `trusttasks.org/spec/` URI written down anywhere is a claim that the registry serves it. Retyped to `spec/acl/list/0.1`, which this crate already binds, with a comment saying why so the next person does not reach for a plausible-looking invention. Neither is a wire change. Both are the census doing its job. Worth recording why CI found this and local runs did not: the census lives in a crate the change barely touches, and its scan roots are `vtc-service/src`, `vta-sdk/src`, `cnm-cli/src` and `vtc-service/admin-ui/src` — `vta-service/src` is not among them, so the identical edit there is invisible to it. Per-crate runs of the crates you edited cannot see any of that. `cargo test --workspace`, which is what CI runs, can — and now passes. Signed-off-by: Glenn Gore <glenn.g@affinidi.com> --------- Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
This was referenced Aug 14, 2026
stormer78
added a commit
that referenced
this pull request
Aug 17, 2026
* release(tsp-js): @openvtc/vti-tsp-js 0.2.0 Publishes the pure-TS HPKE swap (#116) and the new `./hpke` subpath export. Minor rather than patch: `sealBase`/`openBase` and the `./hpke` export are new public surface. The auth-mode `seal`/`open` signatures and the wire bytes are unchanged, so existing callers need no changes. `@openvtc/pnm-core` cannot be published until this is on npm — it imports `@openvtc/vti-tsp-js/hpke`, and the published 0.1.0 exports map has only ".". Signed-off-by: Glenn Gore <glenn.g@affinidi.com> * release(core): @openvtc/pnm-core 0.4.0 Ships the HPKE consolidation (#116) and the trust-task-error 0.x fix (#115). Minor rather than patch: `@hpke/*` leaves the runtime dependency set and the `@openvtc/vti-tsp-js` range gains a floor. The range moves from `*` to `^0.2.0`. `*` expressed no minimum, so it was satisfied by the published 0.1.0 — which has no `./hpke` export — and a consumer whose lockfile pinned 0.1.0 would have installed this release against it and failed to resolve the import. **Do not publish this until `@openvtc/vti-tsp-js@0.2.0` is on npm** (#117). Signed-off-by: Glenn Gore <glenn.g@affinidi.com> --------- Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(core): recognise trust-task-error at any 0.x, not just 0.1 and 0.2
isTrustTaskErrorTypeenumerated two framework error-document versions.trust-tasks-rshas emittedtrust-task-error/0.3since its 0.3 release —it carries the §8.2
inResponseTomember, and0.2's payload schema isadditionalProperties: false, so the document cannot claim to be0.2. TheVTA moved to 0.4 and every rejection it sends has been unrecognised here since.
Unrecognised does not mean "reported with the wrong wording". An error document
this predicate rejects is not an error at all:
parseTrustTaskReplyfallsthrough to the success branch and returns the
ErrorPayloadas the operation'sresult. So a refused task arrived at the calling page as a completed one. A
webvh/dids/updatethe VTA had refused ("did not found: SCID … not found",returned 422) rendered "your agent signed and published the update" in the
hosting UI, and nothing was published.
A
requireConsentrefusal is the same document, which is the more serious half.requestTaskrecognises consent inside a thrownVtaClientError; with nothrow it resolved as
acceptedinstead, so the cross-device match code neverrendered and the approver was never asked. That is silent on both sides — the
requester sees success, the approver sees nothing — and it would have been the
first thing hit by the next VTA to configure an approver set.
Match the framework slug at any
0.xminor instead, which is what SPEC.md §5.2'sforward-minor rule asks of a consumer, so the next minor cannot break this the
same way.
1.xis deliberately excluded: a major bump is where the payload shapemay genuinely change, and that should come back through a code change rather than
be decoded under 0.x assumptions. All four consumers — the REST channel,
parseTrustTaskReply, the approver's decision-outcome reader and push-gatewayregistration — already routed through this one predicate, so they are all fixed.
Why no test caught it: every test that built an error document hard-coded
/0.1, a version no VTA emits.rejecting()now defaults to what actuallyships and the consent tests run across all three versions, so the suite
exercises the wire it will meet. Reverting the predicate fails nine tests,
including the pre-existing consent ones.