//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `trust-ceremony-receipt`. 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())
}
}
}
/**
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": "\nA 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())
})
}
}
/**
Evidence that one enactment of a Trust Ceremony completed (SPEC §4.11, §6.7).
The recorder attests COMPLETENESS AND ORDERING ONLY — never the content of any step. Each enumerated step carries its own issuer's proof and is verifiable independently, so a receipt does not make its recorder a trusted third party: it makes the recorder's claim checkable.
A verifier evaluates the definition's completion rule itself over the enumerated steps. `complete` records what the recorder believed, not what the verifier may assume.*/
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/trust-ceremony-receipt/0.1",
/// "title": "Payload",
/// "description": "\nEvidence that one enactment of a Trust Ceremony completed (SPEC §4.11, §6.7).\n\nThe recorder attests COMPLETENESS AND ORDERING ONLY — never the content of any step. Each enumerated step carries its own issuer's proof and is verifiable independently, so a receipt does not make its recorder a trusted third party: it makes the recorder's claim checkable.\n\nA verifier evaluates the definition's completion rule itself over the enumerated steps. `complete` records what the recorder believed, not what the verifier may assume.",
/// "type": "object",
/// "required": [
/// "complete",
/// "definition",
/// "definitionDigest",
/// "enactment",
/// "steps"
/// ],
/// "properties": {
/// "complete": {
/// "description": "\nWhether the recorder considers the enactment complete.\n\nNOT authoritative. A verifier holding the pinned definition evaluates its completion rule over `steps` directly, and a receipt whose enumerated steps do not satisfy that rule is incomplete however this member is set. It is carried because it distinguishes a recorder that is mistaken from one that is lying — both are visible, and only the second is an attack.",
/// "type": "boolean"
/// },
/// "definition": {
/// "description": "The ceremony definition the enactment ran under (SPEC §6.7). REQUIRED here, unlike on the envelope: a receipt asserts that a flow COMPLETED, and completeness is meaningless without the rule that defines it.",
/// "type": "string",
/// "format": "uri",
/// "minLength": 1
/// },
/// "definitionDigest": {
/// "description": "Multibase-multihash over the RFC 8785 (JCS) canonicalization of that definition. MUST equal the `ceremony.definitionDigest` every enumerated step carried. It is what stops the completion rule being changed after the fact by whoever controls the definition URI.",
/// "$ref": "#/definitions/DigestMultibase"
/// },
/// "enactment": {
/// "description": "The enactment this receipt reports on — the value every enumerated step carries in its `ceremony.enactment` member. Globally unique and never reused (SPEC §4.11).",
/// "type": "string",
/// "minLength": 1
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "parentEnactment": {
/// "description": "The enactment containing this one, where the ceremony was conducted as a step of another. Present so a receipt used as a parent's step evidence names its own place in the tree; one level, as on the envelope member.",
/// "type": "string",
/// "minLength": 1
/// },
/// "salt": {
/// "description": "\nThe per-enactment salt used to compute every step digest, multibase-encoded, with at least 128 bits of entropy.\n\nREQUIRED whenever any step of the enactment carried `ceremony.prev`, because without it the chain cannot be recomputed and the recorder's ordering claim rests on nothing — which is the whole reason `receipt` implies `chained`.\n\nRevealing it here is deliberate. The salt defends against a party who observes a document or a bare digest in transit, which is the threat `task-consent` names for its own salted digest; it is not intended to defend against a party holding this receipt, who is by construction entitled to know the enactment happened and in what order. A ceremony whose step CONTENT must stay hidden from receipt holders wants `enactmentPrivacy: blinded`, not a withheld salt.",
/// "type": "string",
/// "minLength": 22
/// },
/// "steps": {
/// "description": "Every step of the enactment, in the order the recorder observed them. A set containing no step marked `terminal` is a PREFIX, not a completed enactment — which is what makes truncation detectable, since the marker cannot be minted without the terminal step issuer's key.",
/// "type": "array",
/// "items": {
/// "type": "object",
/// "required": [
/// "digestMultibase",
/// "id",
/// "issuer",
/// "step",
/// "typeUri"
/// ],
/// "properties": {
/// "digestMultibase": {
/// "description": "Salted digest of the step document, computed as the specification's Conformance section defines. A verifier holding the document recomputes it; a verifier that does not hold the document still learns that the recorder committed to a specific one.",
/// "$ref": "#/definitions/DigestMultibase"
/// },
/// "id": {
/// "description": "The step document's `id` (SPEC §4.3) — globally unique and never reused, so it names one instance where the step name names a position in the flow, and so a verifier can locate the document the digest is over.",
/// "type": "string",
/// "minLength": 1
/// },
/// "issuer": {
/// "description": "The VID that issued the step document. For a step whose multiplicity is `perRole`, this is what distinguishes one instance from another — N approvers each performing one step are N entries differing only here.",
/// "type": "string",
/// "minLength": 1
/// },
/// "round": {
/// "description": "The document's `ceremony.round`, where the definition permits bounded repetition. Absent means 1. Enumerating rounds separately is what lets a verifier check the repetition bound.",
/// "type": "integer",
/// "minimum": 1.0
/// },
/// "step": {
/// "description": "The step name from the definition, as carried in the document's `ceremony.step`.",
/// "type": "string",
/// "minLength": 1
/// },
/// "terminal": {
/// "description": "Whether the step document carried `ceremony.terminal`. At least one enumerated step MUST carry it for the enactment to be complete.",
/// "type": "boolean"
/// },
/// "typeUri": {
/// "description": "The Type URI the step enacted, including any fragment. Checked against the definition's declared type for that step.",
/// "type": "string",
/// "format": "uri",
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false
/// },
/// "minItems": 1
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Payload {
/**
Whether the recorder considers the enactment complete.
NOT authoritative. A verifier holding the pinned definition evaluates its completion rule over `steps` directly, and a receipt whose enumerated steps do not satisfy that rule is incomplete however this member is set. It is carried because it distinguishes a recorder that is mistaken from one that is lying — both are visible, and only the second is an attack.*/
pub complete: bool,
///The ceremony definition the enactment ran under (SPEC §6.7). REQUIRED here, unlike on the envelope: a receipt asserts that a flow COMPLETED, and completeness is meaningless without the rule that defines it.
pub definition: ::std::string::String,
///Multibase-multihash over the RFC 8785 (JCS) canonicalization of that definition. MUST equal the `ceremony.definitionDigest` every enumerated step carried. It is what stops the completion rule being changed after the fact by whoever controls the definition URI.
#[serde(rename = "definitionDigest")]
pub definition_digest: DigestMultibase,
///The enactment this receipt reports on — the value every enumerated step carries in its `ceremony.enactment` member. Globally unique and never reused (SPEC §4.11).
pub enactment: PayloadEnactment,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///The enactment containing this one, where the ceremony was conducted as a step of another. Present so a receipt used as a parent's step evidence names its own place in the tree; one level, as on the envelope member.
#[serde(
rename = "parentEnactment",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub parent_enactment: ::std::option::Option<PayloadParentEnactment>,
/**
The per-enactment salt used to compute every step digest, multibase-encoded, with at least 128 bits of entropy.
REQUIRED whenever any step of the enactment carried `ceremony.prev`, because without it the chain cannot be recomputed and the recorder's ordering claim rests on nothing — which is the whole reason `receipt` implies `chained`.
Revealing it here is deliberate. The salt defends against a party who observes a document or a bare digest in transit, which is the threat `task-consent` names for its own salted digest; it is not intended to defend against a party holding this receipt, who is by construction entitled to know the enactment happened and in what order. A ceremony whose step CONTENT must stay hidden from receipt holders wants `enactmentPrivacy: blinded`, not a withheld salt.*/
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub salt: ::std::option::Option<PayloadSalt>,
///Every step of the enactment, in the order the recorder observed them. A set containing no step marked `terminal` is a PREFIX, not a completed enactment — which is what makes truncation detectable, since the marker cannot be minted without the terminal step issuer's key.
pub steps: ::std::vec::Vec<PayloadStepsItem>,
}
impl Payload {
pub fn builder() -> builder::Payload {
Default::default()
}
}
///The enactment this receipt reports on — the value every enumerated step carries in its `ceremony.enactment` member. Globally unique and never reused (SPEC §4.11).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The enactment this receipt reports on — the value every enumerated step carries in its `ceremony.enactment` member. Globally unique and never reused (SPEC §4.11).",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadEnactment(::std::string::String);
impl ::std::ops::Deref for PayloadEnactment {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadEnactment> for ::std::string::String {
fn from(value: PayloadEnactment) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadEnactment {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadEnactment {
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 PayloadEnactment {
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 PayloadEnactment {
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 PayloadEnactment {
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 enactment containing this one, where the ceremony was conducted as a step of another. Present so a receipt used as a parent's step evidence names its own place in the tree; one level, as on the envelope member.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The enactment containing this one, where the ceremony was conducted as a step of another. Present so a receipt used as a parent's step evidence names its own place in the tree; one level, as on the envelope member.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadParentEnactment(::std::string::String);
impl ::std::ops::Deref for PayloadParentEnactment {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadParentEnactment> for ::std::string::String {
fn from(value: PayloadParentEnactment) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadParentEnactment {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadParentEnactment {
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 PayloadParentEnactment {
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 PayloadParentEnactment {
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 PayloadParentEnactment {
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 per-enactment salt used to compute every step digest, multibase-encoded, with at least 128 bits of entropy.
REQUIRED whenever any step of the enactment carried `ceremony.prev`, because without it the chain cannot be recomputed and the recorder's ordering claim rests on nothing — which is the whole reason `receipt` implies `chained`.
Revealing it here is deliberate. The salt defends against a party who observes a document or a bare digest in transit, which is the threat `task-consent` names for its own salted digest; it is not intended to defend against a party holding this receipt, who is by construction entitled to know the enactment happened and in what order. A ceremony whose step CONTENT must stay hidden from receipt holders wants `enactmentPrivacy: blinded`, not a withheld salt.*/
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "\nThe per-enactment salt used to compute every step digest, multibase-encoded, with at least 128 bits of entropy.\n\nREQUIRED whenever any step of the enactment carried `ceremony.prev`, because without it the chain cannot be recomputed and the recorder's ordering claim rests on nothing — which is the whole reason `receipt` implies `chained`.\n\nRevealing it here is deliberate. The salt defends against a party who observes a document or a bare digest in transit, which is the threat `task-consent` names for its own salted digest; it is not intended to defend against a party holding this receipt, who is by construction entitled to know the enactment happened and in what order. A ceremony whose step CONTENT must stay hidden from receipt holders wants `enactmentPrivacy: blinded`, not a withheld salt.",
/// "type": "string",
/// "minLength": 22
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadSalt(::std::string::String);
impl ::std::ops::Deref for PayloadSalt {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadSalt> for ::std::string::String {
fn from(value: PayloadSalt) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadSalt {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 22usize {
return Err("shorter than 22 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadSalt {
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 PayloadSalt {
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 PayloadSalt {
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 PayloadSalt {
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())
})
}
}
///`PayloadStepsItem`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "type": "object",
/// "required": [
/// "digestMultibase",
/// "id",
/// "issuer",
/// "step",
/// "typeUri"
/// ],
/// "properties": {
/// "digestMultibase": {
/// "description": "Salted digest of the step document, computed as the specification's Conformance section defines. A verifier holding the document recomputes it; a verifier that does not hold the document still learns that the recorder committed to a specific one.",
/// "$ref": "#/definitions/DigestMultibase"
/// },
/// "id": {
/// "description": "The step document's `id` (SPEC §4.3) — globally unique and never reused, so it names one instance where the step name names a position in the flow, and so a verifier can locate the document the digest is over.",
/// "type": "string",
/// "minLength": 1
/// },
/// "issuer": {
/// "description": "The VID that issued the step document. For a step whose multiplicity is `perRole`, this is what distinguishes one instance from another — N approvers each performing one step are N entries differing only here.",
/// "type": "string",
/// "minLength": 1
/// },
/// "round": {
/// "description": "The document's `ceremony.round`, where the definition permits bounded repetition. Absent means 1. Enumerating rounds separately is what lets a verifier check the repetition bound.",
/// "type": "integer",
/// "minimum": 1.0
/// },
/// "step": {
/// "description": "The step name from the definition, as carried in the document's `ceremony.step`.",
/// "type": "string",
/// "minLength": 1
/// },
/// "terminal": {
/// "description": "Whether the step document carried `ceremony.terminal`. At least one enumerated step MUST carry it for the enactment to be complete.",
/// "type": "boolean"
/// },
/// "typeUri": {
/// "description": "The Type URI the step enacted, including any fragment. Checked against the definition's declared type for that step.",
/// "type": "string",
/// "format": "uri",
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct PayloadStepsItem {
///Salted digest of the step document, computed as the specification's Conformance section defines. A verifier holding the document recomputes it; a verifier that does not hold the document still learns that the recorder committed to a specific one.
#[serde(rename = "digestMultibase")]
pub digest_multibase: DigestMultibase,
///The step document's `id` (SPEC §4.3) — globally unique and never reused, so it names one instance where the step name names a position in the flow, and so a verifier can locate the document the digest is over.
pub id: PayloadStepsItemId,
///The VID that issued the step document. For a step whose multiplicity is `perRole`, this is what distinguishes one instance from another — N approvers each performing one step are N entries differing only here.
pub issuer: PayloadStepsItemIssuer,
///The document's `ceremony.round`, where the definition permits bounded repetition. Absent means 1. Enumerating rounds separately is what lets a verifier check the repetition bound.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub round: ::std::option::Option<::std::num::NonZeroU64>,
///The step name from the definition, as carried in the document's `ceremony.step`.
pub step: PayloadStepsItemStep,
///Whether the step document carried `ceremony.terminal`. At least one enumerated step MUST carry it for the enactment to be complete.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub terminal: ::std::option::Option<bool>,
///The Type URI the step enacted, including any fragment. Checked against the definition's declared type for that step.
#[serde(rename = "typeUri")]
pub type_uri: ::std::string::String,
}
impl PayloadStepsItem {
pub fn builder() -> builder::PayloadStepsItem {
Default::default()
}
}
///The step document's `id` (SPEC §4.3) — globally unique and never reused, so it names one instance where the step name names a position in the flow, and so a verifier can locate the document the digest is over.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The step document's `id` (SPEC §4.3) — globally unique and never reused, so it names one instance where the step name names a position in the flow, and so a verifier can locate the document the digest is over.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadStepsItemId(::std::string::String);
impl ::std::ops::Deref for PayloadStepsItemId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadStepsItemId> for ::std::string::String {
fn from(value: PayloadStepsItemId) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadStepsItemId {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadStepsItemId {
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 PayloadStepsItemId {
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 PayloadStepsItemId {
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 PayloadStepsItemId {
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 VID that issued the step document. For a step whose multiplicity is `perRole`, this is what distinguishes one instance from another — N approvers each performing one step are N entries differing only here.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The VID that issued the step document. For a step whose multiplicity is `perRole`, this is what distinguishes one instance from another — N approvers each performing one step are N entries differing only here.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadStepsItemIssuer(::std::string::String);
impl ::std::ops::Deref for PayloadStepsItemIssuer {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadStepsItemIssuer> for ::std::string::String {
fn from(value: PayloadStepsItemIssuer) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadStepsItemIssuer {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadStepsItemIssuer {
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 PayloadStepsItemIssuer {
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 PayloadStepsItemIssuer {
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 PayloadStepsItemIssuer {
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 step name from the definition, as carried in the document's `ceremony.step`.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The step name from the definition, as carried in the document's `ceremony.step`.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadStepsItemStep(::std::string::String);
impl ::std::ops::Deref for PayloadStepsItemStep {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadStepsItemStep> for ::std::string::String {
fn from(value: PayloadStepsItemStep) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadStepsItemStep {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() < 1usize {
return Err("shorter than 1 characters".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for PayloadStepsItemStep {
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 PayloadStepsItemStep {
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 PayloadStepsItemStep {
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 PayloadStepsItemStep {
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 Payload {
complete: ::std::result::Result<bool, ::std::string::String>,
definition: ::std::result::Result<::std::string::String, ::std::string::String>,
definition_digest: ::std::result::Result<super::DigestMultibase, ::std::string::String>,
enactment: ::std::result::Result<super::PayloadEnactment, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
parent_enactment: ::std::result::Result<
::std::option::Option<super::PayloadParentEnactment>,
::std::string::String,
>,
salt:
::std::result::Result<::std::option::Option<super::PayloadSalt>, ::std::string::String>,
steps:
::std::result::Result<::std::vec::Vec<super::PayloadStepsItem>, ::std::string::String>,
}
impl ::std::default::Default for Payload {
fn default() -> Self {
Self {
complete: Err("no value supplied for complete".to_string()),
definition: Err("no value supplied for definition".to_string()),
definition_digest: Err("no value supplied for definition_digest".to_string()),
enactment: Err("no value supplied for enactment".to_string()),
ext: Ok(Default::default()),
parent_enactment: Ok(Default::default()),
salt: Ok(Default::default()),
steps: Err("no value supplied for steps".to_string()),
}
}
}
impl Payload {
pub fn complete<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<bool>,
T::Error: ::std::fmt::Display,
{
self.complete = value
.try_into()
.map_err(|e| format!("error converting supplied value for complete: {e}"));
self
}
pub fn definition<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::string::String>,
T::Error: ::std::fmt::Display,
{
self.definition = value
.try_into()
.map_err(|e| format!("error converting supplied value for definition: {e}"));
self
}
pub fn definition_digest<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::DigestMultibase>,
T::Error: ::std::fmt::Display,
{
self.definition_digest = value
.try_into()
.map_err(|e| format!("error converting supplied value for definition_digest: {e}"));
self
}
pub fn enactment<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadEnactment>,
T::Error: ::std::fmt::Display,
{
self.enactment = value
.try_into()
.map_err(|e| format!("error converting supplied value for enactment: {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 parent_enactment<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::PayloadParentEnactment>>,
T::Error: ::std::fmt::Display,
{
self.parent_enactment = value
.try_into()
.map_err(|e| format!("error converting supplied value for parent_enactment: {e}"));
self
}
pub fn salt<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::PayloadSalt>>,
T::Error: ::std::fmt::Display,
{
self.salt = value
.try_into()
.map_err(|e| format!("error converting supplied value for salt: {e}"));
self
}
pub fn steps<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::vec::Vec<super::PayloadStepsItem>>,
T::Error: ::std::fmt::Display,
{
self.steps = value
.try_into()
.map_err(|e| format!("error converting supplied value for steps: {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 {
complete: value.complete?,
definition: value.definition?,
definition_digest: value.definition_digest?,
enactment: value.enactment?,
ext: value.ext?,
parent_enactment: value.parent_enactment?,
salt: value.salt?,
steps: value.steps?,
})
}
}
impl ::std::convert::From<super::Payload> for Payload {
fn from(value: super::Payload) -> Self {
Self {
complete: Ok(value.complete),
definition: Ok(value.definition),
definition_digest: Ok(value.definition_digest),
enactment: Ok(value.enactment),
ext: Ok(value.ext),
parent_enactment: Ok(value.parent_enactment),
salt: Ok(value.salt),
steps: Ok(value.steps),
}
}
}
#[derive(Clone, Debug)]
pub struct PayloadStepsItem {
digest_multibase: ::std::result::Result<super::DigestMultibase, ::std::string::String>,
id: ::std::result::Result<super::PayloadStepsItemId, ::std::string::String>,
issuer: ::std::result::Result<super::PayloadStepsItemIssuer, ::std::string::String>,
round: ::std::result::Result<
::std::option::Option<::std::num::NonZeroU64>,
::std::string::String,
>,
step: ::std::result::Result<super::PayloadStepsItemStep, ::std::string::String>,
terminal: ::std::result::Result<::std::option::Option<bool>, ::std::string::String>,
type_uri: ::std::result::Result<::std::string::String, ::std::string::String>,
}
impl ::std::default::Default for PayloadStepsItem {
fn default() -> Self {
Self {
digest_multibase: Err("no value supplied for digest_multibase".to_string()),
id: Err("no value supplied for id".to_string()),
issuer: Err("no value supplied for issuer".to_string()),
round: Ok(Default::default()),
step: Err("no value supplied for step".to_string()),
terminal: Ok(Default::default()),
type_uri: Err("no value supplied for type_uri".to_string()),
}
}
}
impl PayloadStepsItem {
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 id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadStepsItemId>,
T::Error: ::std::fmt::Display,
{
self.id = value
.try_into()
.map_err(|e| format!("error converting supplied value for id: {e}"));
self
}
pub fn issuer<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadStepsItemIssuer>,
T::Error: ::std::fmt::Display,
{
self.issuer = value
.try_into()
.map_err(|e| format!("error converting supplied value for issuer: {e}"));
self
}
pub fn round<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<::std::num::NonZeroU64>>,
T::Error: ::std::fmt::Display,
{
self.round = value
.try_into()
.map_err(|e| format!("error converting supplied value for round: {e}"));
self
}
pub fn step<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::PayloadStepsItemStep>,
T::Error: ::std::fmt::Display,
{
self.step = value
.try_into()
.map_err(|e| format!("error converting supplied value for step: {e}"));
self
}
pub fn terminal<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<bool>>,
T::Error: ::std::fmt::Display,
{
self.terminal = value
.try_into()
.map_err(|e| format!("error converting supplied value for terminal: {e}"));
self
}
pub fn type_uri<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::string::String>,
T::Error: ::std::fmt::Display,
{
self.type_uri = value
.try_into()
.map_err(|e| format!("error converting supplied value for type_uri: {e}"));
self
}
}
impl ::std::convert::TryFrom<PayloadStepsItem> for super::PayloadStepsItem {
type Error = super::error::ConversionError;
fn try_from(
value: PayloadStepsItem,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
digest_multibase: value.digest_multibase?,
id: value.id?,
issuer: value.issuer?,
round: value.round?,
step: value.step?,
terminal: value.terminal?,
type_uri: value.type_uri?,
})
}
}
impl ::std::convert::From<super::PayloadStepsItem> for PayloadStepsItem {
fn from(value: super::PayloadStepsItem) -> Self {
Self {
digest_multibase: Ok(value.digest_multibase),
id: Ok(value.id),
issuer: Ok(value.issuer),
round: Ok(value.round),
step: Ok(value.step),
terminal: Ok(value.terminal),
type_uri: Ok(value.type_uri),
}
}
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/trust-ceremony-receipt/0.1";
const IS_BEARER: bool = true;
const IS_PROOF_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\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 },\n \"$id\": \"https://trusttasks.org/spec/trust-ceremony-receipt/0.1\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"Evidence that one enactment of a Trust Ceremony completed (SPEC §4.11, §6.7).\\n\\nThe recorder attests COMPLETENESS AND ORDERING ONLY — never the content of any step. Each enumerated step carries its own issuer's proof and is verifiable independently, so a receipt does not make its recorder a trusted third party: it makes the recorder's claim checkable.\\n\\nA verifier evaluates the definition's completion rule itself over the enumerated steps. `complete` records what the recorder believed, not what the verifier may assume.\",\n \"properties\": {\n \"complete\": {\n \"description\": \"Whether the recorder considers the enactment complete.\\n\\nNOT authoritative. A verifier holding the pinned definition evaluates its completion rule over `steps` directly, and a receipt whose enumerated steps do not satisfy that rule is incomplete however this member is set. It is carried because it distinguishes a recorder that is mistaken from one that is lying — both are visible, and only the second is an attack.\",\n \"type\": \"boolean\"\n },\n \"definition\": {\n \"description\": \"The ceremony definition the enactment ran under (SPEC §6.7). REQUIRED here, unlike on the envelope: a receipt asserts that a flow COMPLETED, and completeness is meaningless without the rule that defines it.\",\n \"format\": \"uri\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"definitionDigest\": {\n \"$ref\": \"#/$defs/DigestMultibase\",\n \"description\": \"Multibase-multihash over the RFC 8785 (JCS) canonicalization of that definition. MUST equal the `ceremony.definitionDigest` every enumerated step carried. It is what stops the completion rule being changed after the fact by whoever controls the definition URI.\"\n },\n \"enactment\": {\n \"description\": \"The enactment this receipt reports on — the value every enumerated step carries in its `ceremony.enactment` member. Globally unique and never reused (SPEC §4.11).\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"parentEnactment\": {\n \"description\": \"The enactment containing this one, where the ceremony was conducted as a step of another. Present so a receipt used as a parent's step evidence names its own place in the tree; one level, as on the envelope member.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"salt\": {\n \"description\": \"The per-enactment salt used to compute every step digest, multibase-encoded, with at least 128 bits of entropy.\\n\\nREQUIRED whenever any step of the enactment carried `ceremony.prev`, because without it the chain cannot be recomputed and the recorder's ordering claim rests on nothing — which is the whole reason `receipt` implies `chained`.\\n\\nRevealing it here is deliberate. The salt defends against a party who observes a document or a bare digest in transit, which is the threat `task-consent` names for its own salted digest; it is not intended to defend against a party holding this receipt, who is by construction entitled to know the enactment happened and in what order. A ceremony whose step CONTENT must stay hidden from receipt holders wants `enactmentPrivacy: blinded`, not a withheld salt.\",\n \"minLength\": 22,\n \"type\": \"string\"\n },\n \"steps\": {\n \"description\": \"Every step of the enactment, in the order the recorder observed them. A set containing no step marked `terminal` is a PREFIX, not a completed enactment — which is what makes truncation detectable, since the marker cannot be minted without the terminal step issuer's key.\",\n \"items\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"digestMultibase\": {\n \"$ref\": \"#/$defs/DigestMultibase\",\n \"description\": \"Salted digest of the step document, computed as the specification's Conformance section defines. A verifier holding the document recomputes it; a verifier that does not hold the document still learns that the recorder committed to a specific one.\"\n },\n \"id\": {\n \"description\": \"The step document's `id` (SPEC §4.3) — globally unique and never reused, so it names one instance where the step name names a position in the flow, and so a verifier can locate the document the digest is over.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"issuer\": {\n \"description\": \"The VID that issued the step document. For a step whose multiplicity is `perRole`, this is what distinguishes one instance from another — N approvers each performing one step are N entries differing only here.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"round\": {\n \"description\": \"The document's `ceremony.round`, where the definition permits bounded repetition. Absent means 1. Enumerating rounds separately is what lets a verifier check the repetition bound.\",\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"step\": {\n \"description\": \"The step name from the definition, as carried in the document's `ceremony.step`.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"terminal\": {\n \"description\": \"Whether the step document carried `ceremony.terminal`. At least one enumerated step MUST carry it for the enactment to be complete.\",\n \"type\": \"boolean\"\n },\n \"typeUri\": {\n \"description\": \"The Type URI the step enacted, including any fragment. Checked against the definition's declared type for that step.\",\n \"format\": \"uri\",\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"step\",\n \"typeUri\",\n \"issuer\",\n \"id\",\n \"digestMultibase\"\n ],\n \"type\": \"object\"\n },\n \"minItems\": 1,\n \"type\": \"array\"\n }\n },\n \"required\": [\n \"enactment\",\n \"definition\",\n \"definitionDigest\",\n \"complete\",\n \"steps\"\n ],\n \"title\": \"Trust Ceremony Receipt — payload\",\n \"type\": \"object\"\n}\n",
);
}
/// 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] = &[];
#[cfg(test)]
mod conformance {
//! Round-trip tests harvested from the spec's `spec.md`,
//! plus a `rejects_invalid_examples` test for any fixtures
//! in `payload.invalid-examples.json` (validate feature).
/// Each fixture in `payload.invalid-examples.json` MUST be
/// rejected by at least one of: serde deserialization, or
/// JSON-Schema validation under the `validate` feature. The
/// fixture file documents the producer-side bug class that
/// each payload exemplifies; this generated test pins it.
#[cfg(feature = "validate")]
#[test]
fn rejects_invalid_examples() {
use crate::validate::ValidatedPayload;
let fixtures: &[(&str, &str)] = &[
(
"Empty payload omits required members: enactment, definition, definitionDigest, complete, steps.",
"{}",
),
(
"`steps` must have at least one entry. A receipt enumerating nothing attests nothing.",
"{\n \"complete\": true,\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"steps\": []\n}",
),
(
"`definition` is omitted. A receipt asserts completeness, which is meaningless without the rule that defines it — unlike the envelope member, where a definition is optional.",
"{\n \"complete\": true,\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"steps\": [\n {\n \"digestMultibase\": \"zQmW4nR8xKdWqR7fT4sYbGhJ3mLcE8aZuVtHnQrXyB5wCdF\",\n \"id\": \"urn:uuid:1d0a7c31-9b52-4e08-8a6d-3f5c1b9e2074\",\n \"issuer\": \"did:web:applicant.example\",\n \"step\": \"apply\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/submit/0.1\"\n }\n ]\n}",
),
(
"`definitionDigest` is raw hex rather than a multibase-encoded multihash. A digest that hard-codes one algorithm in the wire format is non-conforming.",
"{\n \"complete\": true,\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"3b0c7f1d9e2a5648c1f30b7ae4d2986153ca0f7b8d41e6295af03c8bd71e4a62\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"steps\": [\n {\n \"digestMultibase\": \"zQmW4nR8xKdWqR7fT4sYbGhJ3mLcE8aZuVtHnQrXyB5wCdF\",\n \"id\": \"urn:uuid:1d0a7c31-9b52-4e08-8a6d-3f5c1b9e2074\",\n \"issuer\": \"did:web:applicant.example\",\n \"step\": \"apply\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/submit/0.1\"\n }\n ]\n}",
),
(
"A step omits `digestMultibase`. An enumerated step the recorder has not committed to is not evidence of anything.",
"{\n \"complete\": true,\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"steps\": [\n {\n \"id\": \"urn:uuid:1d0a7c31-9b52-4e08-8a6d-3f5c1b9e2074\",\n \"issuer\": \"did:web:applicant.example\",\n \"step\": \"apply\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/submit/0.1\"\n }\n ]\n}",
),
(
"A step omits `id`, so a verifier cannot locate the document the digest is over.",
"{\n \"complete\": true,\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"steps\": [\n {\n \"digestMultibase\": \"zQmW4nR8xKdWqR7fT4sYbGhJ3mLcE8aZuVtHnQrXyB5wCdF\",\n \"issuer\": \"did:web:applicant.example\",\n \"step\": \"apply\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/submit/0.1\"\n }\n ]\n}",
),
(
"`round` must be at least 1; round 0 is not a repetition.",
"{\n \"complete\": true,\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"steps\": [\n {\n \"digestMultibase\": \"zQmW4nR8xKdWqR7fT4sYbGhJ3mLcE8aZuVtHnQrXyB5wCdF\",\n \"id\": \"urn:uuid:1d0a7c31-9b52-4e08-8a6d-3f5c1b9e2074\",\n \"issuer\": \"did:web:applicant.example\",\n \"round\": 0,\n \"step\": \"supplement\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/status/0.1\"\n }\n ]\n}",
),
(
"A step carries an unknown member (additionalProperties: false). A recorder cannot smuggle an unattested claim into the enumeration.",
"{\n \"complete\": true,\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"steps\": [\n {\n \"__notARealMember__\": true,\n \"digestMultibase\": \"zQmW4nR8xKdWqR7fT4sYbGhJ3mLcE8aZuVtHnQrXyB5wCdF\",\n \"id\": \"urn:uuid:1d0a7c31-9b52-4e08-8a6d-3f5c1b9e2074\",\n \"issuer\": \"did:web:applicant.example\",\n \"step\": \"apply\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/submit/0.1\"\n }\n ]\n}",
),
(
"`salt` is too short to carry 128 bits of entropy in any multibase encoding.",
"{\n \"complete\": true,\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"salt\": \"z6MkShort\",\n \"steps\": [\n {\n \"digestMultibase\": \"zQmW4nR8xKdWqR7fT4sYbGhJ3mLcE8aZuVtHnQrXyB5wCdF\",\n \"id\": \"urn:uuid:1d0a7c31-9b52-4e08-8a6d-3f5c1b9e2074\",\n \"issuer\": \"did:web:applicant.example\",\n \"step\": \"apply\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/submit/0.1\"\n }\n ]\n}",
),
(
"`complete` must be a boolean, not a string. A recorder's determination is a claim, not free text.",
"{\n \"complete\": \"yes\",\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"steps\": [\n {\n \"digestMultibase\": \"zQmW4nR8xKdWqR7fT4sYbGhJ3mLcE8aZuVtHnQrXyB5wCdF\",\n \"id\": \"urn:uuid:1d0a7c31-9b52-4e08-8a6d-3f5c1b9e2074\",\n \"issuer\": \"did:web:applicant.example\",\n \"step\": \"apply\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/submit/0.1\"\n }\n ]\n}",
),
(
"Unknown top-level member is rejected (additionalProperties: false).",
"{\n \"__notARealMember__\": true,\n \"complete\": true,\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"steps\": [\n {\n \"digestMultibase\": \"zQmW4nR8xKdWqR7fT4sYbGhJ3mLcE8aZuVtHnQrXyB5wCdF\",\n \"id\": \"urn:uuid:1d0a7c31-9b52-4e08-8a6d-3f5c1b9e2074\",\n \"issuer\": \"did:web:applicant.example\",\n \"step\": \"apply\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/submit/0.1\"\n }\n ]\n}",
),
(
"`ext` immediate keys must be reverse-DNS namespaced per SPEC §4.5.1.",
"{\n \"complete\": true,\n \"definition\": \"https://trusttasks.org/ceremony/vtc/member-onboarding/0.1\",\n \"definitionDigest\": \"zQmb1XVvHqbCe5nUPFxpJcRz3RtP4pQyKgTsWJgNBzVhE7d\",\n \"enactment\": \"urn:uuid:8f21b0c4-7d3e-4a91-b5c2-1e6f0a9d4b83\",\n \"ext\": {\n \"bareKey\": {\n \"a\": 1\n }\n },\n \"steps\": [\n {\n \"digestMultibase\": \"zQmW4nR8xKdWqR7fT4sYbGhJ3mLcE8aZuVtHnQrXyB5wCdF\",\n \"id\": \"urn:uuid:1d0a7c31-9b52-4e08-8a6d-3f5c1b9e2074\",\n \"issuer\": \"did:web:applicant.example\",\n \"step\": \"apply\",\n \"typeUri\": \"https://trusttasks.org/spec/vtc/join-requests/submit/0.1\"\n }\n ]\n}",
),
];
for (i, (note, raw)) in fixtures.iter().enumerate() {
let value: serde_json::Value = match serde_json::from_str(raw) {
Ok(v) => v,
Err(_) => continue,
};
let serde_ok = serde_json::from_value::<super::Payload>(value.clone()).is_ok();
let schema_ok = super::Payload::validate_value(&value).is_ok();
assert!(
!(serde_ok && schema_ok),
"invalid-example #{} ({:?}) was accepted by both serde and JSON Schema; \
the fixture's stated failure class is no longer caught:\n{}",
i + 1,
note,
raw
);
}
}
}