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
154
155
156
157
//! `consent/*` Trust Task client methods.
//!
//! Drive the VTA consent gate from a messaging bridge (or operator tooling)
//! through the generic dispatcher ([`VtaClient::dispatch_trust_task`]) — there
//! is no dedicated REST route. See `vta-service`'s consent store and the
//! `consent/*` family in the dtgwg registry.
//!
//! `subject` is the platform-agnostic `{platform, conversationRef, kind, agent}`
//! object; `conversationRef` is the bridge's OPAQUE handle — never a raw
//! platform address.
use serde_json::Value;
use super::VtaClient;
use crate::error::VtaError;
use crate::protocols::consent_management::{
ConsentApproverListBody, ConsentApproverSetBody, ConsentDecisionBody, ConsentListBody,
ConsentRequestBody, ConsentRevokeBody,
};
use crate::trust_tasks;
/// Round-trip timeout (seconds) for consent trust tasks.
const CONSENT_TT_TIMEOUT: u64 = 30;
impl VtaClient {
/// `consent/request/1.0` — ask the VTA to gate an inbound conversation for an
/// agent. Default-deny: if no live grant exists, a pending consent is minted
/// for an approver. `scope` is `"receive"` or `"converse"`. `challenge` (≥128
/// bits) is echoed by the matching decision.
///
/// Takes the body rather than positional arguments. The schema declares
/// three optional members and two of them are hints, so the positional form
/// was four `Option<&str>` in a row with `displayHint` and `contextHint`
/// adjacent and interchangeable to the compiler — and it had to break every
/// time the schema grew another optional member. Named fields cost the
/// caller one `..Default::default()` and settle both.
pub async fn consent_request(&self, body: &ConsentRequestBody) -> Result<Value, VtaError> {
let payload = serde_json::to_value(body)?;
self.dispatch_trust_task(
trust_tasks::TASK_CONSENT_REQUEST_1_0,
payload,
CONSENT_TT_TIMEOUT,
)
.await
}
/// `consent/decision/1.0` — allow or deny a conversation; records a grant.
/// `effect` is `"allow"` or `"deny"`; `scope` (`"receive"`/`"converse"`) is
/// required for allow. Echo `challenge` to answer a specific request;
/// `expires_at` is an optional RFC-3339 grant TTL.
pub async fn consent_decision(
&self,
subject: Value,
effect: &str,
scope: Option<&str>,
challenge: Option<&str>,
expires_at: Option<&str>,
) -> Result<Value, VtaError> {
let payload = serde_json::to_value(ConsentDecisionBody {
subject,
effect: effect.to_string(),
scope: scope.map(str::to_string),
challenge: challenge.map(str::to_string),
expires_at: expires_at.map(str::to_string),
})?;
self.dispatch_trust_task(
trust_tasks::TASK_CONSENT_DECISION_1_0,
payload,
CONSENT_TT_TIMEOUT,
)
.await
}
/// `consent/revoke/1.0` — withdraw a standing grant (revert to default-deny).
pub async fn consent_revoke(
&self,
subject: Value,
reason: Option<&str>,
) -> Result<Value, VtaError> {
let payload = serde_json::to_value(ConsentRevokeBody {
subject,
reason: reason.map(str::to_string),
})?;
self.dispatch_trust_task(
trust_tasks::TASK_CONSENT_REVOKE_1_0,
payload,
CONSENT_TT_TIMEOUT,
)
.await
}
/// `consent/list/1.0` — sync / point-check the grants a bridge enforces. All
/// filters are optional; pass a full `subject` for a point-check.
pub async fn consent_list(
&self,
agent: Option<&str>,
platform: Option<&str>,
subject: Option<Value>,
) -> Result<Value, VtaError> {
let payload = serde_json::to_value(ConsentListBody {
agent: agent.map(str::to_string),
platform: platform.map(str::to_string),
subject,
})?;
self.dispatch_trust_task(
trust_tasks::TASK_CONSENT_LIST_1_0,
payload,
CONSENT_TT_TIMEOUT,
)
.await
}
/// `consent/approver-set/1.0` — bind the operator who approves consent for
/// `platform` within `context`, and how the prompt routes (`route` is
/// `"wake"` or `"bridge-relay"`). Admin-gated.
pub async fn consent_approver_set(
&self,
platform: &str,
context: &str,
approver: &str,
route: Option<&str>,
route_hint: Option<&str>,
) -> Result<Value, VtaError> {
let payload = serde_json::to_value(ConsentApproverSetBody {
platform: platform.to_string(),
context: context.to_string(),
approver: approver.to_string(),
route: route.map(str::to_string),
route_hint: route_hint.map(str::to_string),
})?;
self.dispatch_trust_task(
trust_tasks::TASK_CONSENT_APPROVER_SET_1_0,
payload,
CONSENT_TT_TIMEOUT,
)
.await
}
/// `consent/approver-list/1.0` — read the approver bindings, optionally
/// filtered by `platform` / `context`.
pub async fn consent_approver_list(
&self,
platform: Option<&str>,
context: Option<&str>,
) -> Result<Value, VtaError> {
let payload = serde_json::to_value(ConsentApproverListBody {
platform: platform.map(str::to_string),
context: context.map(str::to_string),
})?;
self.dispatch_trust_task(
trust_tasks::TASK_CONSENT_APPROVER_LIST_1_0,
payload,
CONSENT_TT_TIMEOUT,
)
.await
}
}