//! 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"
/// },
/// "context": {
/// "description": "Present with `vta-account`.",
/// "$ref": "#/definitions/ExternalAccountContextId"
/// },
/// "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 `vta-account`.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub context: ::std::option::Option<ExternalAccountContextId>,
///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 context at the community's VTA that owns an external account. The VTA names an account by its context and its identifier, and decides who may use it by act scope in that context: the community must hold `external-auth-use` there, and be bound to the account, for the account to issue it anything. Naming a context grants nothing; the VTA's binding is the gate.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "ExternalAccountContextId",
/// "description": "The context at the community's VTA that owns an external account. The VTA names an account by its context and its identifier, and decides who may use it by act scope in that context: the community must hold `external-auth-use` there, and be bound to the account, for the account to issue it anything. Naming a context grants nothing; the VTA's binding is the gate.",
/// "type": "string",
/// "maxLength": 256,
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct ExternalAccountContextId(::std::string::String);
impl ::std::ops::Deref for ExternalAccountContextId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<ExternalAccountContextId> for ::std::string::String {
fn from(value: ExternalAccountContextId) -> Self {
value.0
}
}
impl ::std::str::FromStr for ExternalAccountContextId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 256usize {
return Err("longer than 256 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 ExternalAccountContextId {
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 ExternalAccountContextId {
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 ExternalAccountContextId {
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 ExternalAccountContextId {
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,
>,
context: ::std::result::Result<
::std::option::Option<super::ExternalAccountContextId>,
::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()),
context: 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 context<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::ExternalAccountContextId>>,
T::Error: ::std::fmt::Display,
{
self.context = value
.try_into()
.map_err(|e| format!("error converting supplied value for context: {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?,
context: value.context?,
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),
context: Ok(value.context),
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 \"context\": {\n \"$ref\": \"#/$defs/ExternalAccountContextId\",\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 \"ExternalAccountContextId\": {\n \"description\": \"The context at the community's VTA that owns an external account. The VTA names an account by its context and its identifier, and decides who may use it by act scope in that context: the community must hold `external-auth-use` there, and be bound to the account, for the account to issue it anything. Naming a context grants nothing; the VTA's binding is the gate.\",\n \"maxLength\": 256,\n \"minLength\": 1,\n \"title\": \"ExternalAccountContextId\",\n \"type\": \"string\"\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 \"context\": {\n \"$ref\": \"#/$defs/ExternalAccountContextId\",\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 \"ExternalAccountContextId\": {\n \"description\": \"The context at the community's VTA that owns an external account. The VTA names an account by its context and its identifier, and decides who may use it by act scope in that context: the community must hold `external-auth-use` there, and be bound to the account, for the account to issue it anything. Naming a context grants nothing; the VTA's binding is the gate.\",\n \"maxLength\": 256,\n \"minLength\": 1,\n \"title\": \"ExternalAccountContextId\",\n \"type\": \"string\"\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,
};
}