use axum::extract::State;
use axum::response::{IntoResponse, Response};
use serde_json::Value;
use trust_tasks_rs::TrustTask;
use crate::auth::AuthClaims;
use crate::error::AppError;
use crate::server::AppState;
mod acl;
mod audit;
mod auth;
mod backup;
mod config;
#[cfg(test)]
mod conformance;
mod consent;
mod consent_request;
mod contexts;
mod cred_vault;
mod credential_exchange;
mod credentials;
mod device;
mod did_templates;
mod discovery;
mod helpers;
mod keys;
mod management;
mod memory;
mod messaging;
#[cfg(all(feature = "webvh", feature = "didcomm"))]
mod passkey_vms;
pub(crate) mod planner;
mod policy_gate;
#[cfg(feature = "webvh")]
mod provision_integration;
mod replay;
mod seeds;
mod task_consent;
pub(crate) mod step_up;
mod step_up_policy;
pub(crate) use step_up::{
AclChangeRoleOp, AclGrantOp, AclRevokeOp, AclSwapKeyOp, ContextDeleteOp, RequireStepUp,
};
mod vault;
#[cfg(feature = "webvh")]
mod webvh;
pub(crate) mod wire_v0_2;
pub(crate) use helpers::TrustTaskOutcome;
use helpers::{body_parse_error_response, method_not_found, reject_with};
use trust_tasks_rs::RejectReason;
#[allow(dead_code)] const REST_ROUTED: &[&str] = vta_sdk::trust_tasks::REST_ROUTED_URIS;
#[allow(dead_code)] const KNOWN_FEATURE_GATED_URIS: &[&str] = &[
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_CHALLENGE_0_1,
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_SUBMIT_0_1,
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_LIST_0_1,
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_REVOKE_0_1,
vta_sdk::trust_tasks::TASK_PROVISION_INTEGRATION_0_2,
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_LIST_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_REGISTER_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_REMOVE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_LIST_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_CREATE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_GET_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_DELETE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_UPDATE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_ROTATE_KEYS_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_REGISTER_WITH_SERVER_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_LIST_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_CHECK_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_SET_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_REMOVE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_DISABLE_1_0,
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_ENABLE_1_0,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_REGISTER_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_PUBLISH_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_DELETE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_ENABLE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_DISABLE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_LIST_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_INFO_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_CHECK_NAME_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_CHANGE_OWNER_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_ROLLBACK_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DID_PROBLEM_REPORT_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_CREATE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_UPDATE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_DISABLE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_PURGE_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_SET_DEFAULT_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_ASSIGN_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_DOMAIN_UNASSIGN_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_SERVER_REGISTER_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_SERVER_HEALTH_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_SERVER_STATS_SYNC_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_REGISTRY_ADMIN_REGISTER_0_1,
vta_sdk::trust_tasks::TASK_DID_MANAGEMENT_REGISTRY_DEREGISTER_0_1,
];
#[allow(dead_code)] const UNSPECCED_DISPATCHED_URIS: &[&str] = &[
"https://trusttasks.org/spec/vta/contexts/list/1.0",
"https://trusttasks.org/spec/vta/contexts/create/1.0",
"https://trusttasks.org/spec/vta/contexts/get/1.0",
"https://trusttasks.org/spec/vta/contexts/update/1.0",
"https://trusttasks.org/spec/vta/contexts/update-did/1.0",
"https://trusttasks.org/spec/vta/contexts/preview-delete/1.0",
"https://trusttasks.org/spec/vta/contexts/delete/1.0",
"https://trusttasks.org/spec/vta/seeds/list/1.0",
"https://trusttasks.org/spec/vta/seeds/rotate/1.0",
"https://trusttasks.org/spec/vta/seeds/export-mnemonic/1.0",
"https://trusttasks.org/spec/vta/audit/get-retention/1.0",
"https://trusttasks.org/spec/vta/audit/update-retention/1.0",
"https://trusttasks.org/spec/vta/discovery/capabilities/1.0",
"https://trusttasks.org/spec/vta/management/reload-services/1.0",
"https://trusttasks.org/spec/vta/backup/initiate-export/1.0",
"https://trusttasks.org/spec/vta/backup/complete-export/1.0",
"https://trusttasks.org/spec/vta/backup/initiate-import/1.0",
"https://trusttasks.org/spec/vta/backup/finalize-import/1.0",
"https://trusttasks.org/spec/vta/backup/abort/1.0",
"https://trusttasks.org/spec/vta/attestation/status/1.0",
"https://trusttasks.org/spec/vta/attestation/report/1.0",
"https://trusttasks.org/spec/vta/webvh/servers/list/1.0",
"https://trusttasks.org/spec/vta/webvh/servers/register/1.0",
"https://trusttasks.org/spec/vta/webvh/servers/remove/1.0",
"https://trusttasks.org/spec/vta/webvh/dids/list/1.0",
"https://trusttasks.org/spec/vta/webvh/dids/create/1.0",
"https://trusttasks.org/spec/vta/webvh/dids/get/1.0",
"https://trusttasks.org/spec/vta/webvh/dids/delete/1.0",
"https://trusttasks.org/spec/vta/webvh/dids/rotate-keys/1.0",
"https://trusttasks.org/spec/vta/webvh/dids/register-with-server/1.0",
"https://trusttasks.org/spec/vta/webvh/agent-name/list/1.0",
"https://trusttasks.org/spec/vta/webvh/agent-name/check/1.0",
"https://trusttasks.org/spec/vta/webvh/agent-name/set/1.0",
"https://trusttasks.org/spec/vta/webvh/agent-name/remove/1.0",
"https://trusttasks.org/spec/vta/webvh/agent-name/disable/1.0",
"https://trusttasks.org/spec/vta/webvh/agent-name/enable/1.0",
"https://trusttasks.org/spec/vault/archive/0.1",
"https://trusttasks.org/spec/vault/unarchive/0.1",
"https://trusttasks.org/spec/vault/restore/0.1",
"https://trusttasks.org/spec/vault/purge/0.1",
"https://trusttasks.org/spec/vault/credentials/receive/0.1",
"https://trusttasks.org/spec/vault/credentials/query/0.1",
"https://trusttasks.org/spec/vault/credentials/get/0.1",
"https://trusttasks.org/spec/vault/credentials/archive/0.1",
"https://trusttasks.org/spec/vault/credentials/unarchive/0.1",
"https://trusttasks.org/spec/vault/credentials/delete/0.1",
"https://trusttasks.org/spec/vault/credentials/restore/0.1",
"https://trusttasks.org/spec/vault/credentials/purge/0.1",
];
macro_rules! dispatch_table {
(
$(
$(#[$meta:meta])*
$($uri:path)|+ => $handler:path
[ $se:ident $disc:ident $acts:literal ]
),+ $(,)?
) => {
#[allow(deprecated)]
async fn dispatch_typed(
state: &AppState,
auth: &AuthClaims,
doc: TrustTask<Value>,
) -> TrustTaskOutcome {
let type_uri = doc.type_uri.to_string();
match type_uri.as_str() {
$(
$(#[$meta])*
$($uri)|+ => $handler(state, auth, doc).await,
)+
_ => method_not_found(doc, &type_uri),
}
}
#[allow(deprecated, dead_code)]
pub(crate) fn class_for(type_uri: &str) -> Option<$crate::policy::TaskClass> {
match type_uri {
$(
$(#[$meta])*
$($uri)|+ => Some($crate::policy::TaskClass::new(
$crate::policy::SideEffectLevel::$se,
$crate::policy::Discloses::$disc,
$acts,
)),
)+
_ => None,
}
}
#[cfg(test)]
#[allow(deprecated)]
fn dispatched_uris() -> Vec<&'static str> {
let mut v: Vec<&'static str> = Vec::new();
$(
$(#[$meta])*
v.extend([$($uri),+]);
)+
v
}
};
}
pub async fn dispatch_trust_task(
auth: AuthClaims,
State(state): State<AppState>,
body: axum::body::Bytes,
) -> Result<Response, AppError> {
Ok(dispatch_trust_task_core(&state, &auth, &body)
.await
.into_response())
}
async fn validate_payload(
state: &AppState,
type_uri: &str,
doc: &TrustTask<Value>,
) -> Option<TrustTaskOutcome> {
let Some(schema) = trust_tasks_rs::schema_index::schema_for(type_uri) else {
if state.config.read().await.policy.require_payload_schema {
return Some(helpers::reject_with(
doc,
RejectReason::MalformedRequest {
reason: format!(
"no payload schema is known for `{type_uri}`, and this VTA is configured \
to refuse tasks it cannot validate"
),
},
));
}
tracing::debug!(
type_uri,
"no payload schema known — dispatching unvalidated (set \
policy.require_payload_schema to refuse instead)"
);
return None;
};
match trust_tasks_rs::validate::against_schema(schema, &doc.payload) {
Ok(()) => None,
Err(e) => {
tracing::info!(type_uri, error = %e, "payload failed schema validation");
Some(helpers::reject_with(
doc,
RejectReason::MalformedRequest {
reason: format!("payload does not conform to {type_uri}: {e}"),
},
))
}
}
}
pub(crate) async fn dispatch_trust_task_core(
state: &AppState,
auth: &AuthClaims,
body: &[u8],
) -> TrustTaskOutcome {
let doc: TrustTask<Value> = match serde_json::from_slice(body) {
Ok(d) => d,
Err(e) => return body_parse_error_response(&e.to_string()),
};
{
let vta_did = state.config.read().await.vta_did.clone();
if let Some(my_vid) = vta_did.as_deref()
&& let Err(reason) = doc.validate_basic(chrono::Utc::now(), my_vid)
{
return reject_with(&doc, reason);
}
}
if !replay::check_and_record(&auth.did, &doc.id) {
return reject_with(
&doc,
RejectReason::TaskFailed {
reason: "duplicate".to_string(),
details: Some(serde_json::json!({
"id": doc.id,
"reason": "this request id was already submitted; the prior submission is \
authoritative — do not retry with the same id",
})),
},
);
}
let _ = auth;
use wire_v0_2::{WIRE_VERSION, WireVersion};
let type_uri = doc.type_uri.to_string();
tracing::info!(
type_uri = %type_uri,
actor = %auth.did,
id = %doc.id,
"trust-task received"
);
let vault_audit = vault_audit_action(&type_uri).map(|action| {
let resource = vault_audit_resource(&doc.payload);
let context_id = doc
.payload
.get("contextId")
.and_then(Value::as_str)
.map(str::to_string);
let detail = doc
.payload
.get("reason")
.and_then(Value::as_str)
.map(str::to_string);
(action, resource, context_id, detail)
});
if let Some(reject) = validate_payload(state, &type_uri, &doc).await {
return reject;
}
let mut delegated_contexts: Vec<String> = Vec::new();
let outcome =
match policy_gate::policy_gate(state, auth, &type_uri, &doc, &mut delegated_contexts).await
{
Some(reject_outcome) => reject_outcome,
None => {
let delegated_auth = (!delegated_contexts.is_empty())
.then(|| auth.with_delegated_authority(&delegated_contexts));
let auth = delegated_auth.as_ref().unwrap_or(auth);
if let Some(spec) = wire_v0_2::lookup_0_2(&type_uri) {
let mut doc = doc;
wire_v0_2::downconvert_request(&mut doc.payload, spec);
if let Ok(uri_0_1) = spec.uri_0_1.parse() {
doc.type_uri = uri_0_1;
}
let outcome = WIRE_VERSION
.scope(WireVersion::V0_2, dispatch_typed(state, auth, doc))
.await;
wire_v0_2::upconvert_response(outcome, spec)
} else {
WIRE_VERSION
.scope(WireVersion::V0_1, dispatch_typed(state, auth, doc))
.await
}
}
};
if let Some((action, resource, context_id, detail)) = vault_audit {
let label = vault_audit_outcome_label(&outcome);
if let Err(e) = crate::audit::record_with_detail(
&state.audit_ks,
&action,
&auth.did,
resource.as_deref(),
&label,
Some(helpers::TRANSPORT_TRUST_TASK),
context_id.as_deref(),
detail.as_deref(),
)
.await
{
tracing::warn!(error = %e, action = %action, "vault audit record failed");
}
}
outcome
}
fn vault_audit_action(type_uri: &str) -> Option<String> {
let rest = type_uri.split("/spec/vault/").nth(1)?;
let segs: Vec<&str> = rest.split('/').filter(|s| !s.is_empty()).collect();
match segs.as_slice() {
["credentials", verb, ..] => Some(format!("vault.cred.{verb}")),
[verb, ..] => Some(format!("vault.{verb}")),
_ => None,
}
}
fn vault_audit_resource(payload: &Value) -> Option<String> {
for key in ["id", "entryId", "credentialId"] {
if let Some(v) = payload.get(key).and_then(Value::as_str) {
return Some(v.to_string());
}
}
None
}
fn vault_audit_outcome_label(outcome: &TrustTaskOutcome) -> String {
if outcome.status.is_success() {
return "success".to_string();
}
if let Ok(v) = serde_json::from_slice::<Value>(&outcome.body)
&& let Some(code) = v
.get("payload")
.and_then(|p| p.get("code"))
.and_then(Value::as_str)
{
return format!("denied:{code}");
}
"denied".to_string()
}
#[cfg(feature = "didcomm")]
pub(crate) fn reject_trust_task(body: &[u8], reason: RejectReason) -> TrustTaskOutcome {
match serde_json::from_slice::<TrustTask<Value>>(body) {
Ok(doc) => reject_with(&doc, reason),
Err(e) => body_parse_error_response(&e.to_string()),
}
}
dispatch_table! {
vta_sdk::trust_tasks::TASK_AUTH_REVOKE_SESSION_0_1 => auth::handle_revoke_session
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_AUTH_WHOAMI_0_1 => auth::handle_whoami
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_AUTH_SESSIONS_LIST_0_1 => auth::handle_sessions_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_AUTH_STEP_UP_APPROVE_RESPONSE_0_1
| vta_sdk::trust_tasks::TASK_AUTH_STEP_UP_APPROVE_RESPONSE_0_2
=> step_up::handle_approve_response
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_AUTH_STEP_UP_POLICY_0_2 => step_up_policy::handle_set_step_up_policy
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_CONSENT_REQUEST_1_0 => consent::handle_request
[ None None false ],
vta_sdk::trust_tasks::TASK_CONSENT_DECISION_1_0 => consent::handle_decision
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_CONSENT_REVOKE_1_0 => consent::handle_revoke
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_CONSENT_LIST_1_0 => consent::handle_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_CONSENT_APPROVER_SET_1_0 => consent::handle_approver_set
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_CONSENT_APPROVER_LIST_1_0 => consent::handle_approver_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_TASK_CONSENT_DECISION_0_1 => task_consent::handle_decision
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_ACL_LIST_0_1 => acl::handle_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_ACL_GRANT_0_1 => acl::handle_create
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_ACL_SHOW_0_1 => acl::handle_get
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_ACL_UPDATE_0_1 => acl::handle_update
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_ACL_CHANGE_ROLE_0_1 => acl::handle_change_role
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_ACL_REVOKE_0_1 => acl::handle_delete
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_ACL_SWAP_KEY_0_1 => acl::handle_swap_key
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_DEVICE_REGISTER_0_1 => device::handle_register
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_DEVICE_HEARTBEAT_0_1 => device::handle_heartbeat
[ None None false ],
vta_sdk::trust_tasks::TASK_DEVICE_LIST_0_1 => device::handle_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_DEVICE_DISABLE_0_1 => device::handle_disable
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_DEVICE_WIPE_0_1 => device::handle_wipe
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_DEVICE_SET_WAKE_0_1 => device::handle_set_wake
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_MESSAGING_PING_0_1 => messaging::handle_ping
[ None None false ],
vta_sdk::trust_tasks::TASK_CONTEXTS_LIST_1_0 => contexts::handle_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_CONTEXTS_CREATE_1_0 => contexts::handle_create
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_CONTEXTS_GET_1_0 => contexts::handle_get
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_CONTEXTS_UPDATE_1_0 => contexts::handle_update
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_CONTEXTS_UPDATE_DID_1_0 => contexts::handle_update_did
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_CONTEXTS_PREVIEW_DELETE_1_0 => contexts::handle_preview_delete
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_CONTEXTS_DELETE_1_0 => contexts::handle_delete
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_KEYS_LIST_0_1 => keys::handle_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_KEYS_CREATE_0_1 => keys::handle_create
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_KEYS_IMPORT_0_1 => keys::handle_import
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_KEYS_SHOW_0_1 => keys::handle_get
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_KEYS_RENAME_0_1 => keys::handle_rename
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_KEYS_REVOKE_0_1 => keys::handle_revoke
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_KEYS_SIGN_0_1 => keys::handle_sign
[ None None true ],
vta_sdk::trust_tasks::TASK_KEYS_DERIVE_AND_SIGN_0_1 => keys::handle_derive_and_sign
[ Mutating None true ],
vta_sdk::trust_tasks::TASK_KEYS_DERIVE_AND_SIGN_DOCUMENT_0_1 => keys::handle_derive_and_sign_document
[ Mutating None true ],
vta_sdk::trust_tasks::TASK_SEEDS_LIST_1_0 => seeds::handle_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_SEEDS_ROTATE_1_0 => seeds::handle_rotate
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_SEEDS_EXPORT_MNEMONIC_1_0 => seeds::handle_export_mnemonic
[ None Secret false ],
vta_sdk::trust_tasks::TASK_AUDIT_LIST_0_1 => audit::handle_list_logs
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_AUDIT_GET_RETENTION_1_0 => audit::handle_get_retention
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_AUDIT_UPDATE_RETENTION_1_0 => audit::handle_update_retention
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_DISCOVERY_CAPABILITIES_1_0 => discovery::handle_capabilities
[ None None false ],
vta_sdk::protocols::credential_exchange::PENDING_LIST
=> credential_exchange::handle_pending_list
[ None Metadata false ],
vta_sdk::protocols::credential_exchange::PENDING_APPROVE
=> credential_exchange::handle_pending_approve
[ Mutating None true ],
vta_sdk::protocols::credential_exchange::PENDING_DENY
=> credential_exchange::handle_pending_deny
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VAULT_LIST_0_1 => vault::handle_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_VAULT_GET_0_1 => vault::handle_get
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_VAULT_UPSERT_0_1 => vault::handle_upsert
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VAULT_DELETE_0_1 => vault::handle_delete
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_VAULT_RELEASE_0_1 => vault::handle_release
[ Mutating Secret false ],
vta_sdk::trust_tasks::TASK_VAULT_PROXY_LOGIN_0_1 => vault::handle_proxy_login
[ Mutating Secret true ],
vta_sdk::trust_tasks::TASK_VAULT_SIGN_TRUST_TASK_0_1 => vault::handle_sign_trust_task
[ Mutating None true ],
vta_sdk::trust_tasks::TASK_VAULT_ARCHIVE_0_1 => vault::handle_archive
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VAULT_UNARCHIVE_0_1 => vault::handle_unarchive
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VAULT_RESTORE_0_1 => vault::handle_restore
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VAULT_PURGE_0_1 => vault::handle_purge
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_VAULT_CREDENTIALS_RECEIVE_0_1 => cred_vault::handle_receive
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VAULT_CREDENTIALS_QUERY_0_1 => cred_vault::handle_query
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_VAULT_CREDENTIALS_GET_0_1 => cred_vault::handle_get
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_VAULT_CREDENTIALS_ARCHIVE_0_1 => cred_vault::handle_archive
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VAULT_CREDENTIALS_UNARCHIVE_0_1 => cred_vault::handle_unarchive
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VAULT_CREDENTIALS_DELETE_0_1 => cred_vault::handle_delete
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_VAULT_CREDENTIALS_RESTORE_0_1 => cred_vault::handle_restore
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VAULT_CREDENTIALS_PURGE_0_1 => cred_vault::handle_purge
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_VTA_CREDENTIALS_ISSUE_0_1 => credentials::handle_issue
[ Mutating None true ],
vta_sdk::trust_tasks::TASK_VTA_CREDENTIALS_REVOKE_0_1 => credentials::handle_revoke
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_VTA_MEMORY_PUT_0_1 => memory::handle_put
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_VTA_MEMORY_LIST_0_1 => memory::handle_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_VTA_MEMORY_DELETE_0_1 => memory::handle_delete
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_CONFIG_SHOW_0_1 => config::handle_get
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_CONFIG_PATCH_0_1 => config::handle_update
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_MANAGEMENT_RELOAD_SERVICES_1_0 => management::handle_reload_services
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_BACKUP_INITIATE_EXPORT_1_0 => backup::handle_initiate_export
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_BACKUP_COMPLETE_EXPORT_1_0 => backup::handle_complete_export
[ Mutating Secret false ],
vta_sdk::trust_tasks::TASK_BACKUP_INITIATE_IMPORT_1_0 => backup::handle_initiate_import
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_BACKUP_FINALIZE_IMPORT_1_0 => backup::handle_finalize_import
[ Destructive None false ],
vta_sdk::trust_tasks::TASK_BACKUP_ABORT_1_0 => backup::handle_abort
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_LIST_2_0 => did_templates::handle_list
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_CREATE_2_0 => did_templates::handle_create
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_GET_2_0 => did_templates::handle_get
[ None Metadata false ],
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_UPDATE_2_0 => did_templates::handle_update
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_DELETE_2_0 => did_templates::handle_delete
[ Mutating None false ],
vta_sdk::trust_tasks::TASK_DID_TEMPLATES_RENDER_2_0 => did_templates::handle_render
[ None Metadata false ],
#[cfg(all(feature = "webvh", feature = "didcomm"))]
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_CHALLENGE_0_1
=> passkey_vms::handle_enroll_challenge
[ None None false ],
#[cfg(all(feature = "webvh", feature = "didcomm"))]
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_SUBMIT_0_1 => passkey_vms::handle_enroll_submit
[ Mutating None false ],
#[cfg(all(feature = "webvh", feature = "didcomm"))]
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_LIST_0_1 => passkey_vms::handle_list
[ None Metadata false ],
#[cfg(all(feature = "webvh", feature = "didcomm"))]
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_REVOKE_0_1 => passkey_vms::handle_revoke
[ Destructive None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_PROVISION_INTEGRATION_0_2
=> provision_integration::handle_request
[ Mutating Secret false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_LIST_1_0 => webvh::handle_servers_list
[ None Metadata false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_REGISTER_1_0 => webvh::handle_servers_register
[ Mutating None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_SERVERS_REMOVE_1_0 => webvh::handle_servers_remove
[ Mutating None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_LIST_1_0 => webvh::handle_dids_list
[ None Metadata false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_CREATE_1_0 => webvh::handle_dids_create
[ Mutating None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_GET_1_0 => webvh::handle_dids_get
[ None Metadata false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_DELETE_1_0 => webvh::handle_dids_delete
[ Destructive None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_UPDATE_1_0 => webvh::handle_dids_update
[ Destructive None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_ROTATE_KEYS_1_0 => webvh::handle_dids_rotate_keys
[ Destructive None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_DIDS_REGISTER_WITH_SERVER_1_0
=> webvh::handle_dids_register_with_server
[ Mutating None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_LIST_1_0 => webvh::handle_agent_name_list
[ None Metadata false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_CHECK_1_0 => webvh::handle_agent_name_check
[ None Metadata false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_SET_1_0 => webvh::handle_agent_name_set
[ Destructive None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_REMOVE_1_0 => webvh::handle_agent_name_remove
[ Destructive None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_DISABLE_1_0 => webvh::handle_agent_name_disable
[ Destructive None false ],
#[cfg(feature = "webvh")]
vta_sdk::trust_tasks::TASK_WEBVH_AGENT_NAME_ENABLE_1_0 => webvh::handle_agent_name_enable
[ Destructive None false ],
}
#[cfg(test)]
mod tests {
use trust_tasks_rs::TrustTask;
use super::*;
#[test]
#[allow(deprecated)]
fn class_for_carries_authoritative_classification() {
use crate::policy::{Discloses, SideEffectLevel};
let release = class_for(vta_sdk::trust_tasks::TASK_VAULT_RELEASE_0_1)
.expect("vault/release is classified");
assert_eq!(release.side_effects, SideEffectLevel::Mutating);
assert_eq!(release.exposure.discloses, Discloses::Secret);
assert!(!release.exposure.acts_as_subject);
let proxy = class_for(vta_sdk::trust_tasks::TASK_VAULT_PROXY_LOGIN_0_1)
.expect("proxy-login is classified");
assert!(
proxy.exposure.acts_as_subject,
"proxy-login acts as the subject"
);
let seed = class_for(vta_sdk::trust_tasks::TASK_SEEDS_EXPORT_MNEMONIC_1_0)
.expect("seed export is classified");
assert_eq!(
seed.exposure.discloses,
Discloses::Secret,
"exporting the mnemonic discloses a secret"
);
assert!(
class_for("https://trusttasks.org/spec/does-not-exist/9.9").is_none(),
"an unknown URI is unclassified — caller applies the floor"
);
}
#[test]
fn body_parse_error_wire_shape() {
let resp = body_parse_error_response("expected `,`");
let _ = resp;
}
#[test]
fn framework_requires_canonical_uri_in_wire_type_field() {
let canonical = serde_json::json!({
"id": "urn:uuid:00000000-0000-0000-0000-000000000001",
"type": "https://trusttasks.org/spec/auth/revoke-session/0.1",
"issuer": "did:example:alice",
"recipient": "did:example:vta",
"issuedAt": "2026-05-20T00:00:00Z",
"payload": { "session_id": "sess-1" }
});
let bytes = serde_json::to_vec(&canonical).unwrap();
let parsed: Result<TrustTask<Value>, _> = serde_json::from_slice(&bytes);
assert!(
parsed.is_ok(),
"canonical URI must parse: {:?}",
parsed.err()
);
let flat = serde_json::json!({
"id": "urn:uuid:00000000-0000-0000-0000-000000000001",
"type": "https://trusttasks.org/vta/auth/revoke-session/1.0",
"issuer": "did:example:alice",
"recipient": "did:example:vta",
"issuedAt": "2026-05-20T00:00:00Z",
"payload": { "session_id": "sess-1" }
});
let bytes = serde_json::to_vec(&flat).unwrap();
let parsed: Result<TrustTask<Value>, _> = serde_json::from_slice(&bytes);
assert!(
parsed.is_err(),
"flat URI must NOT parse — if this changes, the framework \
relaxed its parser and Phase 3 design can simplify"
);
}
#[test]
#[allow(deprecated)] fn phase_2_uri_registry_present() {
let _ = vta_sdk::trust_tasks::TASK_AUTH_CHALLENGE_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_AUTHENTICATE_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_REFRESH_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_REVOKE_SESSION_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_WHOAMI_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_SESSIONS_LIST_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_PASSKEY_LOGIN_START_0_1;
let _ = vta_sdk::trust_tasks::TASK_AUTH_PASSKEY_LOGIN_FINISH_0_1;
}
#[test]
fn dispatcher_handles_every_vta_sdk_uri() {
let dispatched = dispatched_uris();
for declared in vta_sdk::trust_tasks::ALL_URIS {
let in_dispatched = dispatched.contains(declared);
let in_rest_routed = REST_ROUTED.contains(declared);
let in_feature_gated = KNOWN_FEATURE_GATED_URIS.contains(declared);
let in_wire_v0_2 = wire_v0_2::WIRE_V0_2_URIS.contains(declared);
assert!(
in_dispatched || in_rest_routed || in_feature_gated || in_wire_v0_2,
"vta-sdk declares URI `{declared}` but it is not tracked in this dispatcher — \
either (a) add a `dispatch_table!` entry (`URI => slice::handler`), \
(b) add it to `REST_ROUTED` if it lives on a dedicated REST route, \
(c) add it to `KNOWN_FEATURE_GATED_URIS` with a comment explaining the gating, or \
(d) register it in `wire_v0_2::WIRE_V0_2_URIS` if it's an edge-transformed 0.2 URI"
);
}
}
#[test]
fn every_served_uri_has_a_published_spec_or_is_tracked_debt() {
let mut served: std::collections::BTreeSet<&str> = dispatched_uris().into_iter().collect();
served.extend(REST_ROUTED);
served.extend(KNOWN_FEATURE_GATED_URIS);
served.extend(wire_v0_2::WIRE_V0_2_URIS);
let unspecced: Vec<&&str> = served
.iter()
.filter(|uri| {
trust_tasks_rs::schema_index::schema_for(uri).is_none()
&& !UNSPECCED_DISPATCHED_URIS.contains(uri)
})
.collect();
assert!(
unspecced.is_empty(),
"this service serves URIs the published registry (trust-tasks-rs) \
has no spec for, and they are not acknowledged in \
UNSPECCED_DISPATCHED_URIS:\n {}\n\n\
Author the spec upstream in trustoverip/dtgwg-trust-tasks-tf and \
bump trust-tasks-rs — growing the allowlist is the wrong fix \
(see issue #854 and docs/05-design-notes/registry-drift-triage.md).",
unspecced
.iter()
.map(|u| u.to_string())
.collect::<Vec<_>>()
.join("\n ")
);
for uri in UNSPECCED_DISPATCHED_URIS {
assert!(
trust_tasks_rs::schema_index::schema_for(uri).is_none(),
"`{uri}` is now published in the registry — remove it from \
UNSPECCED_DISPATCHED_URIS so the debt shrinks monotonically"
);
assert!(
served.contains(uri),
"`{uri}` is in UNSPECCED_DISPATCHED_URIS but this service no \
longer serves it — remove the stale entry"
);
}
}
#[test]
fn passkey_vms_0_1_dispatched() {
let dispatched = dispatched_uris();
let tracked = |u: &&str| dispatched.contains(u) || KNOWN_FEATURE_GATED_URIS.contains(u);
for v0_1 in [
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_CHALLENGE_0_1,
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_ENROLL_SUBMIT_0_1,
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_LIST_0_1,
vta_sdk::trust_tasks::TASK_PASSKEY_VMS_REVOKE_0_1,
] {
assert!(tracked(&v0_1), "canonical 0.1 URI not dispatched: {v0_1}");
assert!(v0_1.ends_with("/0.1"), "version-label mismatch for {v0_1}");
}
}
#[test]
fn no_uri_is_both_dispatched_and_rest_routed() {
let dispatched = dispatched_uris();
for uri in REST_ROUTED {
assert!(
!dispatched.contains(uri),
"URI `{uri}` is in REST_ROUTED but also in a `dispatch_table!` entry — \
a URI must live on exactly one transport"
);
}
}
}
#[cfg(all(test, feature = "webvh"))]
mod payload_validation_tests {
use serde_json::{Value, json};
use trust_tasks_rs::TrustTask;
const WEBVH_UPDATE: &str = "https://trusttasks.org/spec/vta/webvh/dids/update/1.0";
fn doc(payload: Value) -> TrustTask<Value> {
serde_json::from_value(json!({
"id": "urn:uuid:00000000-0000-0000-0000-000000000042",
"type": WEBVH_UPDATE,
"issuer": "did:key:zTestAdmin",
"recipient": "did:example:vta",
"issuedAt": "2026-07-14T00:00:00Z",
"payload": payload,
}))
.expect("valid trust task")
}
#[tokio::test]
async fn a_precondition_in_the_wrong_case_is_refused_not_ignored() {
let (state, _dir) = crate::test_support::build_signing_test_app_state().await;
let d = doc(json!({
"did": "did:webvh:QmScid:example.com:acme",
"expected_version_id": "3-QmPrior"
}));
let reject = super::validate_payload(&state, WEBVH_UPDATE, &d)
.await
.expect("an unrecognised member must be refused");
let body: Value = serde_json::from_slice(&reject.body).unwrap();
let msg = body.to_string();
assert!(
msg.contains("does not conform"),
"expected a schema-conformance refusal, got: {msg}"
);
}
#[tokio::test]
async fn the_correct_casing_passes() {
let (state, _dir) = crate::test_support::build_signing_test_app_state().await;
let d = doc(json!({
"did": "did:webvh:QmScid:example.com:acme",
"document": { "id": "did:webvh:QmScid:example.com:acme" },
"expectedVersionId": "3-QmPrior"
}));
assert!(
super::validate_payload(&state, WEBVH_UPDATE, &d)
.await
.is_none()
);
}
#[tokio::test]
async fn the_ext_slot_the_relay_stamps_an_origin_into_is_permitted() {
let (state, _dir) = crate::test_support::build_signing_test_app_state().await;
let d = doc(json!({
"did": "did:webvh:QmScid:example.com:acme",
"ext": { "openvtc.origin": "https://control.example.com" }
}));
assert!(
super::validate_payload(&state, WEBVH_UPDATE, &d)
.await
.is_none(),
"closed payloads must still admit `ext`, or the relay cannot stamp an origin"
);
}
#[tokio::test]
async fn an_invented_member_is_refused() {
let (state, _dir) = crate::test_support::build_signing_test_app_state().await;
let d = doc(json!({ "did": "did:webvh:x", "skipApproval": true }));
assert!(
super::validate_payload(&state, WEBVH_UPDATE, &d)
.await
.is_some()
);
}
#[tokio::test]
async fn an_unspecced_task_proceeds_by_default_and_can_be_refused() {
let (state, _dir) = crate::test_support::build_signing_test_app_state().await;
const UNSPECCED: &str = "https://trusttasks.org/spec/vta/webvh/dids/create/1.0";
let d = doc(json!({ "contextId": "default" }));
assert!(
super::validate_payload(&state, UNSPECCED, &d)
.await
.is_none(),
"by default an unvalidatable task still dispatches — refusing it would \
break the many tasks that have no spec yet"
);
state.config.write().await.policy.require_payload_schema = true;
assert!(
super::validate_payload(&state, UNSPECCED, &d)
.await
.is_some(),
"an operator who would rather fail closed can"
);
}
}