1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
//! The TSP transport binding: how a Trust Task is carried in a TSP payload.
//!
//! One module, both directions, **one workspace**. Inbound frames are opened
//! here and outbound ones wrapped here, so the binding is a single fact rather
//! than a convention each path remembers. That is the whole point of a binding
//! being a *thing* — the next transport added should be a module beside this
//! one, not an edit spread across every sender and receiver.
//!
//! ## Why this lives in the SDK
//!
//! It was `vta-service`'s, which made it a single fact about *one side*. The
//! VTA's receiver started requiring the envelope while every Rust client in
//! this workspace — `session::TspSession`, `didcomm_session`'s TSP leg, and so
//! the `pnm health` probe, the mobile approver and `VtaClient`'s TSP trust
//! tasks — kept sealing the bare document, and the service refused every one of
//! them:
//!
//! ```text
//! refused a TSP frame that is not a binding envelope
//! reason=TSP payload is not a `…/binding/tsp/0.1/envelope` envelope
//! (got `…/spec/messaging/ping/0.1`)
//! ```
//!
//! A binding that only one end of the workspace can see is the same private
//! dialect it exists to end. `vta-sdk` is the leaf both sides already depend
//! on, so it is where a fact shared by both belongs.
//!
//! Spec: `https://trusttasks.org/binding/tsp/0.1`, and the constant comes from
//! `trust-tasks-tsp` rather than a literal here so it moves when the binding
//! does.
//!
//! ## Who this binding is *not* for
//!
//! The recipient decides, and today exactly one peer on the TSP wire does not
//! speak it: the **mediator's own management surface**. Its TSP arm parses the
//! bare document and claims it only when the type is one it serves
//! (`affinidi-messaging-mediator`'s `trust_tasks::parse_if_served`), so it has
//! no envelope to open and would file a wrapped frame as ordinary mail. That is
//! why [`crate::acl_setup::set_client_acl_over_tsp`] sends bare and must keep
//! doing so until the mediator adopts the binding. Every VTA/VTC-addressed
//! Trust Task goes through the wrapper.
use Value;
/// The TSP binding's envelope type URI, re-exported so a caller that needs to
/// name it (a test, a log line) takes it from the same place the wrapper does.
pub use ENVELOPE_TYPE;
/// The TSP binding's payload wrapper: `{"type": ENVELOPE_TYPE, "document": …}`.
///
/// ## Why TSP has a wrapper when the other two bindings do not
///
/// Each binding has to say "this payload is a Trust Task" somewhere the
/// framework can read before parsing. HTTPS says it with the request path
/// (`POST …/trust-tasks`); DIDComm says it with the message `type`. TSP has
/// neither — a TSP message carries a sender VID, a recipient VID and opaque
/// bytes — so the binding puts it in the JSON. The wrapper is not ceremony; it
/// is the only place TSP has to put it.
///
/// ## What this replaces
///
/// Both ends of this workspace used to seal the **bare document**, and said so
/// out loud: this module's own header called the payload "identical to the REST
/// body", and the browser wallet's `tsp-channel.ts` carried the comment "TSP
/// plaintext = the Trust-Task envelope JSON (no binding wrapper)". They agreed
/// with each other and with nothing else. A conformant peer built on
/// `trust-tasks-tsp` would have rejected every frame with `WrongEnvelopeType`,
/// and we would have rejected all of theirs — and neither side could have used
/// the binding crate at all, which is what makes "a new transport is a new
/// binding" untrue in practice.
/// Open a TSP binding envelope, returning the Trust-Task document bytes.
///
/// A payload that is not an envelope, or carries the wrong `type`, is refused
/// rather than read as a document: accepting a bare document "just in case"
/// would keep the private dialect alive on the wire for as long as anyone spoke
/// it, and nothing is deployed that needs the kindness.
///
/// The two sides act on a refusal differently, and both are right. The VTA
/// *answers* it — the sender VID is proven, so a `malformedRequest` envelope
/// naming the carriage tells a misconfigured peer exactly what is wrong. A
/// client *skips* it: the frame arrived on a socket that also carries mediator
/// traffic and control frames, so "not our binding" there means "not addressed
/// to this layer", not "malformed".