//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `vtc/storage/credentials/set`. Version: `0.1`.
#[allow(unused_imports)]
use serde::{Deserialize, Serialize};
/// Error types.
pub mod error {
/// Error from a `TryFrom` or `FromStr` implementation.
pub struct ConversionError(::std::borrow::Cow<'static, str>);
impl ::std::error::Error for ConversionError {}
impl ::std::fmt::Display for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::fmt::Debug for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Debug::fmt(&self.0, f)
}
}
impl From<&'static str> for ConversionError {
fn from(value: &'static str) -> Self {
Self(value.into())
}
}
impl From<String> for ConversionError {
fn from(value: String) -> Self {
Self(value.into())
}
}
}
///Whether a config can authenticate now, and how — never the credential. `status`: `vta-account`, `ambient` and `sealed` are usable; `required` means the config cannot store until a credential is provided (a `sealed` config never set, or set before a restore, which carries no credential); `none` is a `local` config.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "CredentialState",
/// "description": "Whether a config can authenticate now, and how — never the credential. `status`: `vta-account`, `ambient` and `sealed` are usable; `required` means the config cannot store until a credential is provided (a `sealed` config never set, or set before a restore, which carries no credential); `none` is a `local` config.",
/// "type": "object",
/// "required": [
/// "status"
/// ],
/// "properties": {
/// "account": {
/// "description": "Present with `vta-account`.",
/// "$ref": "#/definitions/ExternalAccountId"
/// },
/// "fingerprint": {
/// "description": "Present with `sealed`: a digest identifying the stored credential, so an administrator can tell which one is set without anyone being able to read it. Computed over the credential with a community-held key, never a bare hash of a low-entropy secret.",
/// "$ref": "#/definitions/DigestMultibase"
/// },
/// "roomsHoldingCredentials": {
/// "description": "Present with `vta-account`: how many rooms the community currently holds a live room-scoped credential for.",
/// "type": "integer",
/// "minimum": 0.0
/// },
/// "setAt": {
/// "description": "Present with `sealed`: when the stored credential was last set.",
/// "type": "string",
/// "format": "date-time"
/// },
/// "status": {
/// "type": "string",
/// "enum": [
/// "vta-account",
/// "ambient",
/// "sealed",
/// "required",
/// "none"
/// ]
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct CredentialState {
///Present with `vta-account`.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub account: ::std::option::Option<ExternalAccountId>,
///Present with `sealed`: a digest identifying the stored credential, so an administrator can tell which one is set without anyone being able to read it. Computed over the credential with a community-held key, never a bare hash of a low-entropy secret.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub fingerprint: ::std::option::Option<DigestMultibase>,
///Present with `vta-account`: how many rooms the community currently holds a live room-scoped credential for.
#[serde(
rename = "roomsHoldingCredentials",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub rooms_holding_credentials: ::std::option::Option<u64>,
///Present with `sealed`: when the stored credential was last set.
#[serde(
rename = "setAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub set_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
pub status: CredentialStateStatus,
}
impl CredentialState {
pub fn builder() -> builder::CredentialState {
Default::default()
}
}
///`CredentialStateStatus`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "enum": [
/// "vta-account",
/// "ambient",
/// "sealed",
/// "required",
/// "none"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum CredentialStateStatus {
#[serde(rename = "vta-account")]
VtaAccount,
#[serde(rename = "ambient")]
Ambient,
#[serde(rename = "sealed")]
Sealed,
#[serde(rename = "required")]
Required,
#[serde(rename = "none")]
None,
}
impl ::std::fmt::Display for CredentialStateStatus {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::VtaAccount => f.write_str("vta-account"),
Self::Ambient => f.write_str("ambient"),
Self::Sealed => f.write_str("sealed"),
Self::Required => f.write_str("required"),
Self::None => f.write_str("none"),
}
}
}
impl ::std::str::FromStr for CredentialStateStatus {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"vta-account" => Ok(Self::VtaAccount),
"ambient" => Ok(Self::Ambient),
"sealed" => Ok(Self::Sealed),
"required" => Ok(Self::Required),
"none" => Ok(Self::None),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for CredentialStateStatus {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for CredentialStateStatus {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for CredentialStateStatus {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
/**
A cryptographic digest as a multibase-encoded multihash — the encoding the W3C Verifiable Credentials Data Model 2.0 defines for `digestMultibase`, and the one `did:webvh` uses for its SCID and entry hashes.
Multihash carries the hash algorithm in-band, so the value is self-describing and the wire format survives an algorithm change without a schema revision; multibase does the same for the base encoding, so a verifier never infers base58 from base64url by context. A bare hex string or a `sha-256:`-style prefix hard-codes one algorithm into the wire contract and is non-conforming here.
This definition constrains the *encoding only*. What the digest is computed over is stated by each referencing field, because it differs legitimately: a digest over a JSON document is taken over its RFC 8785 (JCS) canonicalization, while a digest over an opaque artifact is taken over its bytes. A field whose input is a JSON document and which does not name a canonicalization is not reproducible.
Restricted to the two multibase headers W3C Controlled Identifiers 1.0 §2.4 normatively requires — `z` (base58btc) and `u` (base64url-no-pad). CID permits others but states that "interoperability is not guaranteed between implementations using such values", and a registry whose purpose is interoperability should not mint digests a conforming verifier may be unable to read. The alphabets are enforced rather than assumed: base58btc excludes 0, O, I and l, and an earlier permissive pattern let three published examples carry digests that were not valid base58 at all. base58btc is RECOMMENDED, for consistency with `did:key` and `did:webvh`.*/
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "DigestMultibase",
/// "description": "A cryptographic digest as a multibase-encoded multihash — the encoding the W3C Verifiable Credentials Data Model 2.0 defines for `digestMultibase`, and the one `did:webvh` uses for its SCID and entry hashes.\n\nMultihash carries the hash algorithm in-band, so the value is self-describing and the wire format survives an algorithm change without a schema revision; multibase does the same for the base encoding, so a verifier never infers base58 from base64url by context. A bare hex string or a `sha-256:`-style prefix hard-codes one algorithm into the wire contract and is non-conforming here.\n\nThis definition constrains the *encoding only*. What the digest is computed over is stated by each referencing field, because it differs legitimately: a digest over a JSON document is taken over its RFC 8785 (JCS) canonicalization, while a digest over an opaque artifact is taken over its bytes. A field whose input is a JSON document and which does not name a canonicalization is not reproducible.\n\nRestricted to the two multibase headers W3C Controlled Identifiers 1.0 §2.4 normatively requires — `z` (base58btc) and `u` (base64url-no-pad). CID permits others but states that \"interoperability is not guaranteed between implementations using such values\", and a registry whose purpose is interoperability should not mint digests a conforming verifier may be unable to read. The alphabets are enforced rather than assumed: base58btc excludes 0, O, I and l, and an earlier permissive pattern let three published examples carry digests that were not valid base58 at all. base58btc is RECOMMENDED, for consistency with `did:key` and `did:webvh`.",
/// "examples": [
/// "zQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR"
/// ],
/// "type": "string",
/// "minLength": 16,
/// "pattern": "^(z[1-9A-HJ-NP-Za-km-z]+|u[A-Za-z0-9_-]+)$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct DigestMultibase(::std::string::String);
impl ::std::ops::Deref for DigestMultibase {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<DigestMultibase> for ::std::string::String {
fn from(value: DigestMultibase) -> Self {
value.0
}
}
impl ::std::str::FromStr for DigestMultibase {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 16usize {
return Err("shorter than 16 characters".into());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^(z[1-9A-HJ-NP-Za-km-z]+|u[A-Za-z0-9_-]+)$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err(
"doesn't match pattern \"^(z[1-9A-HJ-NP-Za-km-z]+|u[A-Za-z0-9_-]+)$\"".into(),
);
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for DigestMultibase {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for DigestMultibase {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for DigestMultibase {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for DigestMultibase {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Ext",
/// "description": "Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.",
/// "type": "object",
/// "minProperties": 1,
/// "additionalProperties": true,
/// "propertyNames": {
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
/// }
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(transparent)]
pub struct Ext(pub ::std::collections::HashMap<ExtKey, ::serde_json::Value>);
impl ::std::ops::Deref for Ext {
type Target = ::std::collections::HashMap<ExtKey, ::serde_json::Value>;
fn deref(&self) -> &::std::collections::HashMap<ExtKey, ::serde_json::Value> {
&self.0
}
}
impl ::std::convert::From<Ext> for ::std::collections::HashMap<ExtKey, ::serde_json::Value> {
fn from(value: Ext) -> Self {
value.0
}
}
impl ::std::convert::From<::std::collections::HashMap<ExtKey, ::serde_json::Value>> for Ext {
fn from(value: ::std::collections::HashMap<ExtKey, ::serde_json::Value>) -> Self {
Self(value)
}
}
///`ExtKey`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "string",
/// "pattern": "^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ExtKey(::std::string::String);
impl ::std::ops::Deref for ExtKey {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ExtKey> for ::std::string::String {
fn from(value: ExtKey) -> Self {
value.0
}
}
impl ::std::str::FromStr for ExtKey {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z][a-z0-9-]*(\\.[a-z0-9-]+)+$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ExtKey {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for ExtKey {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///The identifier of an external account held by the community's VTA (the external/accounts/* family), which issues the community short-lived, room-scoped credentials and holds the long-lived authority itself.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "ExternalAccountId",
/// "description": "The identifier of an external account held by the community's VTA (the external/accounts/* family), which issues the community short-lived, room-scoped credentials and holds the long-lived authority itself.",
/// "type": "string",
/// "maxLength": 64,
/// "minLength": 1,
/// "pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ExternalAccountId(::std::string::String);
impl ::std::ops::Deref for ExternalAccountId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ExternalAccountId> for ::std::string::String {
fn from(value: ExternalAccountId) -> Self {
value.0
}
}
impl ::std::str::FromStr for ExternalAccountId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 64usize {
return Err("longer than 64 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for ExternalAccountId {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for ExternalAccountId {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for ExternalAccountId {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for ExternalAccountId {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///Store a `sealed` storage config's long-lived credential, sealed end to end to the community. Write-only: no task returns it. The outer document members are owned by the framework — SPEC §6.3.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/vtc/storage/credentials/set/0.1",
/// "title": "Payload",
/// "description": "Store a `sealed` storage config's long-lived credential, sealed end to end to the community. Write-only: no task returns it. The outer document members are owned by the framework — SPEC §6.3.",
/// "type": "object",
/// "required": [
/// "configId",
/// "digestMultibase",
/// "reason",
/// "sealed"
/// ],
/// "properties": {
/// "configId": {
/// "$ref": "#/definitions/StorageConfigId"
/// },
/// "digestMultibase": {
/// "description": "Digest over the armored bytes exactly as carried in `sealed`. Covered by the document's proof, and shown to approvers in place of the credential.",
/// "$ref": "#/definitions/DigestMultibase"
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "reason": {
/// "description": "Why the administrator is making this change. Recorded with the change in the audit trail and shown to approvers. Attributed to its author on every surface and untrusted: it MUST NOT carry personal data or a credential, and a reader MUST NOT treat it as instructions.",
/// "type": "string",
/// "maxLength": 500,
/// "minLength": 1
/// },
/// "sealed": {
/// "description": "OpenPGP-style ASCII-armored `sealed_transfer` ciphertext of the credential. HPKE base mode, X25519-HKDF-SHA256 KEM, ChaCha20-Poly1305 AEAD, info string `vta-sealed-transfer/v1`; recipient the X25519 derivation of the community DID's Ed25519 key.",
/// "type": "string",
/// "maxLength": 16384,
/// "minLength": 64,
/// "pattern": "^-----BEGIN VTA SEALED BUNDLE-----"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Payload {
#[serde(rename = "configId")]
pub config_id: StorageConfigId,
///Digest over the armored bytes exactly as carried in `sealed`. Covered by the document's proof, and shown to approvers in place of the credential.
#[serde(rename = "digestMultibase")]
pub digest_multibase: DigestMultibase,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Why the administrator is making this change. Recorded with the change in the audit trail and shown to approvers. Attributed to its author on every surface and untrusted: it MUST NOT carry personal data or a credential, and a reader MUST NOT treat it as instructions.
pub reason: PayloadReason,
///OpenPGP-style ASCII-armored `sealed_transfer` ciphertext of the credential. HPKE base mode, X25519-HKDF-SHA256 KEM, ChaCha20-Poly1305 AEAD, info string `vta-sealed-transfer/v1`; recipient the X25519 derivation of the community DID's Ed25519 key.
pub sealed: PayloadSealed,
}
impl Payload {
pub fn builder() -> builder::Payload {
Default::default()
}
}
///Why the administrator is making this change. Recorded with the change in the audit trail and shown to approvers. Attributed to its author on every surface and untrusted: it MUST NOT carry personal data or a credential, and a reader MUST NOT treat it as instructions.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Why the administrator is making this change. Recorded with the change in the audit trail and shown to approvers. Attributed to its author on every surface and untrusted: it MUST NOT carry personal data or a credential, and a reader MUST NOT treat it as instructions.",
/// "type": "string",
/// "maxLength": 500,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadReason(::std::string::String);
impl ::std::ops::Deref for PayloadReason {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadReason> for ::std::string::String {
fn from(value: PayloadReason) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadReason {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 500usize {
return Err("longer than 500 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadReason {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadReason {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadReason {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadReason {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///OpenPGP-style ASCII-armored `sealed_transfer` ciphertext of the credential. HPKE base mode, X25519-HKDF-SHA256 KEM, ChaCha20-Poly1305 AEAD, info string `vta-sealed-transfer/v1`; recipient the X25519 derivation of the community DID's Ed25519 key.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "OpenPGP-style ASCII-armored `sealed_transfer` ciphertext of the credential. HPKE base mode, X25519-HKDF-SHA256 KEM, ChaCha20-Poly1305 AEAD, info string `vta-sealed-transfer/v1`; recipient the X25519 derivation of the community DID's Ed25519 key.",
/// "type": "string",
/// "maxLength": 16384,
/// "minLength": 64,
/// "pattern": "^-----BEGIN VTA SEALED BUNDLE-----"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadSealed(::std::string::String);
impl ::std::ops::Deref for PayloadSealed {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadSealed> for ::std::string::String {
fn from(value: PayloadSealed) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadSealed {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 16384usize {
return Err("longer than 16384 characters".into());
}
if value.chars().count() < 64usize {
return Err("shorter than 64 characters".into());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^-----BEGIN VTA SEALED BUNDLE-----").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^-----BEGIN VTA SEALED BUNDLE-----\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadSealed {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for PayloadSealed {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for PayloadSealed {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for PayloadSealed {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///`Response`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "type": "object",
/// "required": [
/// "configId",
/// "credentialState"
/// ],
/// "properties": {
/// "configId": {
/// "$ref": "#/definitions/StorageConfigId"
/// },
/// "credentialState": {
/// "$ref": "#/definitions/CredentialState"
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "probeOk": {
/// "description": "Present when the community probed the config with the new credential.",
/// "type": "boolean"
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Response {
#[serde(rename = "configId")]
pub config_id: StorageConfigId,
#[serde(rename = "credentialState")]
pub credential_state: CredentialState,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Present when the community probed the config with the new credential.
#[serde(
rename = "probeOk",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub probe_ok: ::std::option::Option<bool>,
}
impl Response {
pub fn builder() -> builder::Response {
Default::default()
}
}
///A storage config's identifier, chosen by the administrator who creates it and immutable thereafter: lowercase letters, digits and inner hyphens, at most 64 characters (`eu-s3-primary`, `walrus-main`). Unique within the community. Never reused after a config is retired, because blobs name their config by this value.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "StorageConfigId",
/// "description": "A storage config's identifier, chosen by the administrator who creates it and immutable thereafter: lowercase letters, digits and inner hyphens, at most 64 characters (`eu-s3-primary`, `walrus-main`). Unique within the community. Never reused after a config is retired, because blobs name their config by this value.",
/// "type": "string",
/// "maxLength": 64,
/// "minLength": 1,
/// "pattern": "^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct StorageConfigId(::std::string::String);
impl ::std::ops::Deref for StorageConfigId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<StorageConfigId> for ::std::string::String {
fn from(value: StorageConfigId) -> Self {
value.0
}
}
impl ::std::str::FromStr for StorageConfigId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 64usize {
return Err("longer than 64 characters".into());
}
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
static PATTERN: ::std::sync::LazyLock<::regress::Regex> =
::std::sync::LazyLock::new(|| {
::regress::Regex::new("^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$").unwrap()
});
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for StorageConfigId {
type Error = self::error::ConversionError;
fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<&::std::string::String> for StorageConfigId {
type Error = self::error::ConversionError;
fn try_from(
value: &::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::convert::TryFrom<::std::string::String> for StorageConfigId {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl<'de> ::serde::Deserialize<'de> for StorageConfigId {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
/// Types for composing complex structures.
pub mod builder {
#[derive(Clone, Debug)]
pub struct CredentialState {
account: ::std::result::Result<
::std::option::Option<super::ExternalAccountId>,
::std::string::String,
>,
fingerprint: ::std::result::Result<
::std::option::Option<super::DigestMultibase>,
::std::string::String,
>,
rooms_holding_credentials:
::std::result::Result<::std::option::Option<u64>, ::std::string::String>,
set_at: ::std::result::Result<
::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
::std::string::String,
>,
status: ::std::result::Result<super::CredentialStateStatus, ::std::string::String>,
}
impl ::std::default::Default for CredentialState {
fn default() -> Self {
Self {
account: Ok(Default::default()),
fingerprint: Ok(Default::default()),
rooms_holding_credentials: Ok(Default::default()),
set_at: Ok(Default::default()),
status: Err("no value supplied for status".to_string()),
}
}
}
impl CredentialState {
pub fn account<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::ExternalAccountId>>,
T::Error: ::std::fmt::Display,
{
self.account = value
.try_into()
.map_err(|e| format!("error converting supplied value for account: {e}"));
self
}
pub fn fingerprint<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::DigestMultibase>>,
T::Error: ::std::fmt::Display,
{
self.fingerprint = value
.try_into()
.map_err(|e| format!("error converting supplied value for fingerprint: {e}"));
self
}
pub fn rooms_holding_credentials<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<u64>>,
T::Error: ::std::fmt::Display,
{
self.rooms_holding_credentials = value.try_into().map_err(|e| {
format!("error converting supplied value for rooms_holding_credentials: {e}")
});
self
}
pub fn set_at<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<
::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
>,
T::Error: ::std::fmt::Display,
{
self.set_at = value
.try_into()
.map_err(|e| format!("error converting supplied value for set_at: {e}"));
self
}
pub fn status<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::CredentialStateStatus>,
T::Error: ::std::fmt::Display,
{
self.status = value
.try_into()
.map_err(|e| format!("error converting supplied value for status: {e}"));
self
}
}
impl ::std::convert::TryFrom<CredentialState> for super::CredentialState {
type Error = super::error::ConversionError;
fn try_from(
value: CredentialState,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
account: value.account?,
fingerprint: value.fingerprint?,
rooms_holding_credentials: value.rooms_holding_credentials?,
set_at: value.set_at?,
status: value.status?,
})
}
}
impl ::std::convert::From<super::CredentialState> for CredentialState {
fn from(value: super::CredentialState) -> Self {
Self {
account: Ok(value.account),
fingerprint: Ok(value.fingerprint),
rooms_holding_credentials: Ok(value.rooms_holding_credentials),
set_at: Ok(value.set_at),
status: Ok(value.status),
}
}
}
#[derive(Clone, Debug)]
pub struct Payload {
config_id: ::std::result::Result<super::StorageConfigId, ::std::string::String>,
digest_multibase: ::std::result::Result<super::DigestMultibase, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
reason: ::std::result::Result<super::PayloadReason, ::std::string::String>,
sealed: ::std::result::Result<super::PayloadSealed, ::std::string::String>,
}
impl ::std::default::Default for Payload {
fn default() -> Self {
Self {
config_id: Err("no value supplied for config_id".to_string()),
digest_multibase: Err("no value supplied for digest_multibase".to_string()),
ext: Ok(Default::default()),
reason: Err("no value supplied for reason".to_string()),
sealed: Err("no value supplied for sealed".to_string()),
}
}
}
impl Payload {
pub fn config_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::StorageConfigId>,
T::Error: ::std::fmt::Display,
{
self.config_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for config_id: {e}"));
self
}
pub fn digest_multibase<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::DigestMultibase>,
T::Error: ::std::fmt::Display,
{
self.digest_multibase = value
.try_into()
.map_err(|e| format!("error converting supplied value for digest_multibase: {e}"));
self
}
pub fn ext<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Ext>>,
T::Error: ::std::fmt::Display,
{
self.ext = value
.try_into()
.map_err(|e| format!("error converting supplied value for ext: {e}"));
self
}
pub fn reason<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadReason>,
T::Error: ::std::fmt::Display,
{
self.reason = value
.try_into()
.map_err(|e| format!("error converting supplied value for reason: {e}"));
self
}
pub fn sealed<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadSealed>,
T::Error: ::std::fmt::Display,
{
self.sealed = value
.try_into()
.map_err(|e| format!("error converting supplied value for sealed: {e}"));
self
}
}
impl ::std::convert::TryFrom<Payload> for super::Payload {
type Error = super::error::ConversionError;
fn try_from(value: Payload) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
config_id: value.config_id?,
digest_multibase: value.digest_multibase?,
ext: value.ext?,
reason: value.reason?,
sealed: value.sealed?,
})
}
}
impl ::std::convert::From<super::Payload> for Payload {
fn from(value: super::Payload) -> Self {
Self {
config_id: Ok(value.config_id),
digest_multibase: Ok(value.digest_multibase),
ext: Ok(value.ext),
reason: Ok(value.reason),
sealed: Ok(value.sealed),
}
}
}
#[derive(Clone, Debug)]
pub struct Response {
config_id: ::std::result::Result<super::StorageConfigId, ::std::string::String>,
credential_state: ::std::result::Result<super::CredentialState, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
probe_ok: ::std::result::Result<::std::option::Option<bool>, ::std::string::String>,
}
impl ::std::default::Default for Response {
fn default() -> Self {
Self {
config_id: Err("no value supplied for config_id".to_string()),
credential_state: Err("no value supplied for credential_state".to_string()),
ext: Ok(Default::default()),
probe_ok: Ok(Default::default()),
}
}
}
impl Response {
pub fn config_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::StorageConfigId>,
T::Error: ::std::fmt::Display,
{
self.config_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for config_id: {e}"));
self
}
pub fn credential_state<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::CredentialState>,
T::Error: ::std::fmt::Display,
{
self.credential_state = value
.try_into()
.map_err(|e| format!("error converting supplied value for credential_state: {e}"));
self
}
pub fn ext<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::Ext>>,
T::Error: ::std::fmt::Display,
{
self.ext = value
.try_into()
.map_err(|e| format!("error converting supplied value for ext: {e}"));
self
}
pub fn probe_ok<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<bool>>,
T::Error: ::std::fmt::Display,
{
self.probe_ok = value
.try_into()
.map_err(|e| format!("error converting supplied value for probe_ok: {e}"));
self
}
}
impl ::std::convert::TryFrom<Response> for super::Response {
type Error = super::error::ConversionError;
fn try_from(value: Response) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
config_id: value.config_id?,
credential_state: value.credential_state?,
ext: value.ext?,
probe_ok: value.probe_ok?,
})
}
}
impl ::std::convert::From<super::Response> for Response {
fn from(value: super::Response) -> Self {
Self {
config_id: Ok(value.config_id),
credential_state: Ok(value.credential_state),
ext: Ok(value.ext),
probe_ok: Ok(value.probe_ok),
}
}
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/vtc/storage/credentials/set/0.1";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"CredentialState\": {\n \"additionalProperties\": false,\n \"description\": \"Whether a config can authenticate now, and how — never the credential. `status`: `vta-account`, `ambient` and `sealed` are usable; `required` means the config cannot store until a credential is provided (a `sealed` config never set, or set before a restore, which carries no credential); `none` is a `local` config.\",\n \"properties\": {\n \"account\": {\n \"$ref\": \"#/$defs/ExternalAccountId\",\n \"description\": \"Present with `vta-account`.\"\n },\n \"fingerprint\": {\n \"$ref\": \"#/$defs/DigestMultibase\",\n \"description\": \"Present with `sealed`: a digest identifying the stored credential, so an administrator can tell which one is set without anyone being able to read it. Computed over the credential with a community-held key, never a bare hash of a low-entropy secret.\"\n },\n \"roomsHoldingCredentials\": {\n \"description\": \"Present with `vta-account`: how many rooms the community currently holds a live room-scoped credential for.\",\n \"minimum\": 0,\n \"type\": \"integer\"\n },\n \"setAt\": {\n \"description\": \"Present with `sealed`: when the stored credential was last set.\",\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"status\": {\n \"enum\": [\n \"vta-account\",\n \"ambient\",\n \"sealed\",\n \"required\",\n \"none\"\n ],\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"title\": \"CredentialState\",\n \"type\": \"object\"\n },\n \"DigestMultibase\": {\n \"description\": \"A cryptographic digest as a multibase-encoded multihash — the encoding the W3C Verifiable Credentials Data Model 2.0 defines for `digestMultibase`, and the one `did:webvh` uses for its SCID and entry hashes.\\n\\nMultihash carries the hash algorithm in-band, so the value is self-describing and the wire format survives an algorithm change without a schema revision; multibase does the same for the base encoding, so a verifier never infers base58 from base64url by context. A bare hex string or a `sha-256:`-style prefix hard-codes one algorithm into the wire contract and is non-conforming here.\\n\\nThis definition constrains the *encoding only*. What the digest is computed over is stated by each referencing field, because it differs legitimately: a digest over a JSON document is taken over its RFC 8785 (JCS) canonicalization, while a digest over an opaque artifact is taken over its bytes. A field whose input is a JSON document and which does not name a canonicalization is not reproducible.\\n\\nRestricted to the two multibase headers W3C Controlled Identifiers 1.0 §2.4 normatively requires — `z` (base58btc) and `u` (base64url-no-pad). CID permits others but states that \\\"interoperability is not guaranteed between implementations using such values\\\", and a registry whose purpose is interoperability should not mint digests a conforming verifier may be unable to read. The alphabets are enforced rather than assumed: base58btc excludes 0, O, I and l, and an earlier permissive pattern let three published examples carry digests that were not valid base58 at all. base58btc is RECOMMENDED, for consistency with `did:key` and `did:webvh`.\",\n \"examples\": [\n \"zQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR\"\n ],\n \"minLength\": 16,\n \"pattern\": \"^(z[1-9A-HJ-NP-Za-km-z]+|u[A-Za-z0-9_-]+)$\",\n \"title\": \"DigestMultibase\",\n \"type\": \"string\"\n },\n \"Ext\": {\n \"additionalProperties\": true,\n \"description\": \"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\n \"minProperties\": 1,\n \"propertyNames\": {\n \"pattern\": \"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"\n },\n \"title\": \"Ext\",\n \"type\": \"object\"\n },\n \"ExternalAccountId\": {\n \"description\": \"The identifier of an external account held by the community's VTA (the external/accounts/* family), which issues the community short-lived, room-scoped credentials and holds the long-lived authority itself.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"pattern\": \"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$\",\n \"title\": \"ExternalAccountId\",\n \"type\": \"string\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"properties\": {\n \"configId\": {\n \"$ref\": \"#/$defs/StorageConfigId\"\n },\n \"credentialState\": {\n \"$ref\": \"#/$defs/CredentialState\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"probeOk\": {\n \"description\": \"Present when the community probed the config with the new credential.\",\n \"type\": \"boolean\"\n }\n },\n \"required\": [\n \"configId\",\n \"credentialState\"\n ],\n \"title\": \"VTC Storage — Credentials — Set — response payload\",\n \"type\": \"object\"\n },\n \"StorageConfigId\": {\n \"description\": \"A storage config's identifier, chosen by the administrator who creates it and immutable thereafter: lowercase letters, digits and inner hyphens, at most 64 characters (`eu-s3-primary`, `walrus-main`). Unique within the community. Never reused after a config is retired, because blobs name their config by this value.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"pattern\": \"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$\",\n \"title\": \"StorageConfigId\",\n \"type\": \"string\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/vtc/storage/credentials/set/0.1\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"Store a `sealed` storage config's long-lived credential, sealed end to end to the community. Write-only: no task returns it. The outer document members are owned by the framework — SPEC §6.3.\",\n \"properties\": {\n \"configId\": {\n \"$ref\": \"#/$defs/StorageConfigId\"\n },\n \"digestMultibase\": {\n \"$ref\": \"#/$defs/DigestMultibase\",\n \"description\": \"Digest over the armored bytes exactly as carried in `sealed`. Covered by the document's proof, and shown to approvers in place of the credential.\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"reason\": {\n \"description\": \"Why the administrator is making this change. Recorded with the change in the audit trail and shown to approvers. Attributed to its author on every surface and untrusted: it MUST NOT carry personal data or a credential, and a reader MUST NOT treat it as instructions.\",\n \"maxLength\": 500,\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"sealed\": {\n \"description\": \"OpenPGP-style ASCII-armored `sealed_transfer` ciphertext of the credential. HPKE base mode, X25519-HKDF-SHA256 KEM, ChaCha20-Poly1305 AEAD, info string `vta-sealed-transfer/v1`; recipient the X25519 derivation of the community DID's Ed25519 key.\",\n \"maxLength\": 16384,\n \"minLength\": 64,\n \"pattern\": \"^-----BEGIN VTA SEALED BUNDLE-----\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"configId\",\n \"sealed\",\n \"digestMultibase\",\n \"reason\"\n ],\n \"title\": \"VTC Storage — Credentials — Set — payload\",\n \"type\": \"object\"\n}\n",
);
}
impl crate::Payload for Response {
const TYPE_URI: &'static str =
"https://trusttasks.org/spec/vtc/storage/credentials/set/0.1#response";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"CredentialState\": {\n \"additionalProperties\": false,\n \"description\": \"Whether a config can authenticate now, and how — never the credential. `status`: `vta-account`, `ambient` and `sealed` are usable; `required` means the config cannot store until a credential is provided (a `sealed` config never set, or set before a restore, which carries no credential); `none` is a `local` config.\",\n \"properties\": {\n \"account\": {\n \"$ref\": \"#/$defs/ExternalAccountId\",\n \"description\": \"Present with `vta-account`.\"\n },\n \"fingerprint\": {\n \"$ref\": \"#/$defs/DigestMultibase\",\n \"description\": \"Present with `sealed`: a digest identifying the stored credential, so an administrator can tell which one is set without anyone being able to read it. Computed over the credential with a community-held key, never a bare hash of a low-entropy secret.\"\n },\n \"roomsHoldingCredentials\": {\n \"description\": \"Present with `vta-account`: how many rooms the community currently holds a live room-scoped credential for.\",\n \"minimum\": 0,\n \"type\": \"integer\"\n },\n \"setAt\": {\n \"description\": \"Present with `sealed`: when the stored credential was last set.\",\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"status\": {\n \"enum\": [\n \"vta-account\",\n \"ambient\",\n \"sealed\",\n \"required\",\n \"none\"\n ],\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"status\"\n ],\n \"title\": \"CredentialState\",\n \"type\": \"object\"\n },\n \"DigestMultibase\": {\n \"description\": \"A cryptographic digest as a multibase-encoded multihash — the encoding the W3C Verifiable Credentials Data Model 2.0 defines for `digestMultibase`, and the one `did:webvh` uses for its SCID and entry hashes.\\n\\nMultihash carries the hash algorithm in-band, so the value is self-describing and the wire format survives an algorithm change without a schema revision; multibase does the same for the base encoding, so a verifier never infers base58 from base64url by context. A bare hex string or a `sha-256:`-style prefix hard-codes one algorithm into the wire contract and is non-conforming here.\\n\\nThis definition constrains the *encoding only*. What the digest is computed over is stated by each referencing field, because it differs legitimately: a digest over a JSON document is taken over its RFC 8785 (JCS) canonicalization, while a digest over an opaque artifact is taken over its bytes. A field whose input is a JSON document and which does not name a canonicalization is not reproducible.\\n\\nRestricted to the two multibase headers W3C Controlled Identifiers 1.0 §2.4 normatively requires — `z` (base58btc) and `u` (base64url-no-pad). CID permits others but states that \\\"interoperability is not guaranteed between implementations using such values\\\", and a registry whose purpose is interoperability should not mint digests a conforming verifier may be unable to read. The alphabets are enforced rather than assumed: base58btc excludes 0, O, I and l, and an earlier permissive pattern let three published examples carry digests that were not valid base58 at all. base58btc is RECOMMENDED, for consistency with `did:key` and `did:webvh`.\",\n \"examples\": [\n \"zQmbWqxBEKC3P8tqsKc98xmWNzrzDtRLMiMPL8wBuTGsMnR\"\n ],\n \"minLength\": 16,\n \"pattern\": \"^(z[1-9A-HJ-NP-Za-km-z]+|u[A-Za-z0-9_-]+)$\",\n \"title\": \"DigestMultibase\",\n \"type\": \"string\"\n },\n \"Ext\": {\n \"additionalProperties\": true,\n \"description\": \"Vendor-namespaced extension object per SPEC.md §4.5.1. Each immediate key MUST be a reverse-DNS namespace; structure under each namespace is opaque to the framework.\",\n \"minProperties\": 1,\n \"propertyNames\": {\n \"pattern\": \"^[a-z][a-z0-9-]*(\\\\.[a-z0-9-]+)+$\"\n },\n \"title\": \"Ext\",\n \"type\": \"object\"\n },\n \"ExternalAccountId\": {\n \"description\": \"The identifier of an external account held by the community's VTA (the external/accounts/* family), which issues the community short-lived, room-scoped credentials and holds the long-lived authority itself.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"pattern\": \"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$\",\n \"title\": \"ExternalAccountId\",\n \"type\": \"string\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"properties\": {\n \"configId\": {\n \"$ref\": \"#/$defs/StorageConfigId\"\n },\n \"credentialState\": {\n \"$ref\": \"#/$defs/CredentialState\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"probeOk\": {\n \"description\": \"Present when the community probed the config with the new credential.\",\n \"type\": \"boolean\"\n }\n },\n \"required\": [\n \"configId\",\n \"credentialState\"\n ],\n \"title\": \"VTC Storage — Credentials — Set — response payload\",\n \"type\": \"object\"\n },\n \"StorageConfigId\": {\n \"description\": \"A storage config's identifier, chosen by the administrator who creates it and immutable thereafter: lowercase letters, digits and inner hyphens, at most 64 characters (`eu-s3-primary`, `walrus-main`). Unique within the community. Never reused after a config is retired, because blobs name their config by this value.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"pattern\": \"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$\",\n \"title\": \"StorageConfigId\",\n \"type\": \"string\"\n }\n },\n \"$ref\": \"#/$defs/Response\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\n}\n",
);
}
impl crate::RequestPayload for Payload {
type Response = Response;
}
/// The extended error codes this specification declares (SPEC §7.3 item 9,
/// §8.5), in declaration order. Empty when it declares none.
pub const ERROR_CODES: &[crate::DeclaredErrorCode] = &[
error_codes::NOT_FOUND,
error_codes::NOT_SEALED_MODE,
error_codes::CANNOT_OPEN,
error_codes::INVALID_CREDENTIAL,
];
/// One constant per extended error code this specification declares
/// (SPEC §7.3 item 9), named for its local part.
///
/// Emit these rather than a string literal: the code is read from the
/// specification, so it cannot name a code the specification never
/// declared.
pub mod error_codes {
/// `vtc/storage/credentials/set:notFound`
///
/// The community has no storage config with this identifier.
///
/// Declared `retryable: false`.
pub const NOT_FOUND: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "vtc/storage/credentials/set:notFound",
retryable: false,
details_schema: ::core::option::Option::None,
};
/// `vtc/storage/credentials/set:notSealedMode`
///
/// The config's `auth.mode` is not `sealed`. Credentials for a `vta-account` config are the VTA's, set there; an `ambient` config has none.
///
/// Declared `retryable: false`.
pub const NOT_SEALED_MODE: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "vtc/storage/credentials/set:notSealedMode",
retryable: false,
details_schema: ::core::option::Option::None,
};
/// `vtc/storage/credentials/set:cannotOpen`
///
/// The sealed block is not sealed to this community, is malformed, or its digest does not match `digestMultibase`. Nothing was stored.
///
/// Declared `retryable: false`.
pub const CANNOT_OPEN: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "vtc/storage/credentials/set:cannotOpen",
retryable: false,
details_schema: ::core::option::Option::None,
};
/// `vtc/storage/credentials/set:invalidCredential`
///
/// The block opened, but what it carries is not a credential for the config's kind (see Definitions). Nothing was stored.
///
/// Declared `retryable: false`.
pub const INVALID_CREDENTIAL: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "vtc/storage/credentials/set:invalidCredential",
retryable: false,
details_schema: ::core::option::Option::None,
};
}