//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `vta/app-state/put-many`. Version: `1.0`.
#[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())
}
}
}
///Optimistic-concurrency precondition on a write. A positive value requires that the record's current `version` equals it exactly. Zero means "create only" — the write applies only if no LIVE record exists at the address, which is what makes lease acquisition safe: without it two instances can each read "absent", each write, and each believe it won. A tombstone is not a live record, so `expectedVersion: 0` succeeds over one; the created record takes the namespace's next counter value, which is necessarily greater than the tombstone's.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "ExpectedVersion",
/// "description": "Optimistic-concurrency precondition on a write. A positive value requires that the record's current `version` equals it exactly. Zero means \"create only\" — the write applies only if no LIVE record exists at the address, which is what makes lease acquisition safe: without it two instances can each read \"absent\", each write, and each believe it won. A tombstone is not a live record, so `expectedVersion: 0` succeeds over one; the created record takes the namespace's next counter value, which is necessarily greater than the tombstone's.",
/// "type": "integer",
/// "minimum": 0.0
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(transparent)]
pub struct ExpectedVersion(pub u64);
impl ::std::ops::Deref for ExpectedVersion {
type Target = u64;
fn deref(&self) -> &u64 {
&self.0
}
}
impl ::std::convert::From<ExpectedVersion> for u64 {
fn from(value: ExpectedVersion) -> Self {
value.0
}
}
impl ::std::convert::From<u64> for ExpectedVersion {
fn from(value: u64) -> Self {
Self(value)
}
}
impl ::std::str::FromStr for ExpectedVersion {
type Err = <u64 as ::std::str::FromStr>::Err;
fn from_str(value: &str) -> ::std::result::Result<Self, Self::Err> {
Ok(Self(value.parse()?))
}
}
impl ::std::convert::TryFrom<&str> for ExpectedVersion {
type Error = <u64 as ::std::str::FromStr>::Err;
fn try_from(value: &str) -> ::std::result::Result<Self, Self::Error> {
value.parse()
}
}
impl ::std::convert::TryFrom<String> for ExpectedVersion {
type Error = <u64 as ::std::str::FromStr>::Err;
fn try_from(value: String) -> ::std::result::Result<Self, Self::Error> {
value.parse()
}
}
impl ::std::fmt::Display for ExpectedVersion {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
self.0.fmt(f)
}
}
///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())
})
}
}
///Application-chosen identifier for a record within a namespace. Opaque to the maintainer: it MUST NOT be parsed, normalized, or case-folded, and prefix matching in `list` is a byte-prefix comparison over the UTF-8 encoding. Applications SHOULD use `/`-delimited hierarchical keys (`community/acme`, `contact/z6Mk…`) so that `prefix` can address a record family, but the delimiter is a convention between an application and itself — the maintainer attaches no meaning to it.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Key",
/// "description": "Application-chosen identifier for a record within a namespace. Opaque to the maintainer: it MUST NOT be parsed, normalized, or case-folded, and prefix matching in `list` is a byte-prefix comparison over the UTF-8 encoding. Applications SHOULD use `/`-delimited hierarchical keys (`community/acme`, `contact/z6Mk…`) so that `prefix` can address a record family, but the delimiter is a convention between an application and itself — the maintainer attaches no meaning to it.",
/// "type": "string",
/// "maxLength": 512,
/// "minLength": 1,
/// "pattern": "^[^\\u0000]+$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct Key(::std::string::String);
impl ::std::ops::Deref for Key {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<Key> for ::std::string::String {
fn from(value: Key) -> Self {
value.0
}
}
impl ::std::str::FromStr for Key {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
if value.chars().count() > 512usize {
return Err("longer than 512 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("^[^\\u0000]+$").unwrap());
if PATTERN.find(value).is_none() {
return Err("doesn't match pattern \"^[^\\u0000]+$\"".into());
}
Ok(Self(value.to_string()))
}
}
impl ::std::convert::TryFrom<&str> for Key {
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 Key {
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 Key {
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 Key {
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())
})
}
}
///Scopes one application's records within a context, so several tools can share a context without colliding — `openvtc`, `cnm`, an agent runtime. The maintainer MUST NOT interpret the value; it is an opaque partition name. Namespaces are first-come and unreserved, so an application SHOULD pick a stable, specific one: a future per-namespace ACL would grant on this exact string, which makes renaming a namespace a migration rather than an edit.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Namespace",
/// "description": "Scopes one application's records within a context, so several tools can share a context without colliding — `openvtc`, `cnm`, an agent runtime. The maintainer MUST NOT interpret the value; it is an opaque partition name. Namespaces are first-come and unreserved, so an application SHOULD pick a stable, specific one: a future per-namespace ACL would grant on this exact string, which makes renaming a namespace a migration rather than an edit.",
/// "type": "string",
/// "maxLength": 64,
/// "minLength": 1,
/// "pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$"
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct Namespace(::std::string::String);
impl ::std::ops::Deref for Namespace {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<Namespace> for ::std::string::String {
fn from(value: Namespace) -> Self {
value.0
}
}
impl ::std::str::FromStr for Namespace {
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-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 Namespace {
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 Namespace {
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 Namespace {
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 Namespace {
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())
})
}
}
///Write up to 64 application-state records in one round trip, each carrying its own optimistic-concurrency precondition. The batch `mode` decides what a single failure costs: `independent` (the default) applies each write on its own merits, `atomic` applies all or none.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/vta/app-state/put-many/1.0",
/// "title": "Payload",
/// "description": "Write up to 64 application-state records in one round trip, each carrying its own optimistic-concurrency precondition. The batch `mode` decides what a single failure costs: `independent` (the default) applies each write on its own merits, `atomic` applies all or none.",
/// "type": "object",
/// "required": [
/// "contextId",
/// "namespace",
/// "writes"
/// ],
/// "properties": {
/// "contextId": {
/// "description": "The VTA context the records are scoped to; the isolation boundary.",
/// "type": "string",
/// "minLength": 1
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// },
/// "mode": {
/// "description": "`independent` applies each write on its own merits, so one conflicted record does not block the other nine — what a flush of unrelated edits wants, and why it is the default. `atomic` applies all or none, for records carrying a joint invariant. An atomic DEFAULT would let one stale record silently wedge an entire flush, and a caller could not tell a wedged flush from a slow one.",
/// "default": "independent",
/// "type": "string",
/// "enum": [
/// "independent",
/// "atomic"
/// ],
/// "$comment": "The default is load-bearing rather than a convenience; see the spec's Abstract."
/// },
/// "namespace": {
/// "description": "One namespace per batch. Atomicity is only meaningful within the counter the writes take their versions from, and that counter is per (contextId, namespace).",
/// "$ref": "#/definitions/Namespace"
/// },
/// "writes": {
/// "description": "The writes to apply. Keys MUST be distinct: two writes to one key in a batch have no defined order and are refused rather than serialised.",
/// "type": "array",
/// "items": {
/// "$ref": "#/definitions/Write"
/// },
/// "maxItems": 64,
/// "minItems": 1
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Payload {
///The VTA context the records are scoped to; the isolation boundary.
#[serde(rename = "contextId")]
pub context_id: PayloadContextId,
///Ecosystem-defined extension members per SPEC.md §4.5.1.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///`independent` applies each write on its own merits, so one conflicted record does not block the other nine — what a flush of unrelated edits wants, and why it is the default. `atomic` applies all or none, for records carrying a joint invariant. An atomic DEFAULT would let one stale record silently wedge an entire flush, and a caller could not tell a wedged flush from a slow one.
#[serde(default = "defaults::payload_mode")]
pub mode: PayloadMode,
///One namespace per batch. Atomicity is only meaningful within the counter the writes take their versions from, and that counter is per (contextId, namespace).
pub namespace: Namespace,
///The writes to apply. Keys MUST be distinct: two writes to one key in a batch have no defined order and are refused rather than serialised.
pub writes: ::std::vec::Vec<Write>,
}
///The VTA context the records are scoped to; the isolation boundary.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The VTA context the records are scoped to; the isolation boundary.",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadContextId(::std::string::String);
impl ::std::ops::Deref for PayloadContextId {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadContextId> for ::std::string::String {
fn from(value: PayloadContextId) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadContextId {
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 PayloadContextId {
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 PayloadContextId {
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 PayloadContextId {
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 PayloadContextId {
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())
})
}
}
///`independent` applies each write on its own merits, so one conflicted record does not block the other nine — what a flush of unrelated edits wants, and why it is the default. `atomic` applies all or none, for records carrying a joint invariant. An atomic DEFAULT would let one stale record silently wedge an entire flush, and a caller could not tell a wedged flush from a slow one.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "`independent` applies each write on its own merits, so one conflicted record does not block the other nine — what a flush of unrelated edits wants, and why it is the default. `atomic` applies all or none, for records carrying a joint invariant. An atomic DEFAULT would let one stale record silently wedge an entire flush, and a caller could not tell a wedged flush from a slow one.",
/// "default": "independent",
/// "type": "string",
/// "enum": [
/// "independent",
/// "atomic"
/// ],
/// "$comment": "The default is load-bearing rather than a convenience; see the spec's Abstract."
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum PayloadMode {
#[serde(rename = "independent")]
Independent,
#[serde(rename = "atomic")]
Atomic,
}
impl ::std::fmt::Display for PayloadMode {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Independent => f.write_str("independent"),
Self::Atomic => f.write_str("atomic"),
}
}
}
impl ::std::str::FromStr for PayloadMode {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"independent" => Ok(Self::Independent),
"atomic" => Ok(Self::Atomic),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for PayloadMode {
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 PayloadMode {
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 PayloadMode {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
impl ::std::default::Default for PayloadMode {
fn default() -> Self {
PayloadMode::Independent
}
}
///Success response to vta/app-state/put-many in `independent` mode. Type https://trusttasks.org/spec/vta/app-state/put-many/1.0#response. A response is returned even when some writes conflicted, because the task did what it promised — applied each write on its own merits — and the per-record outcomes are the answer rather than the failure. An `atomic` batch that does not apply is a trust-task-error carrying vta/app-state/put-many:atomicBatchRejected, whose details carry the same per-record outcomes.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "description": "Success response to vta/app-state/put-many in `independent` mode. Type https://trusttasks.org/spec/vta/app-state/put-many/1.0#response. A response is returned even when some writes conflicted, because the task did what it promised — applied each write on its own merits — and the per-record outcomes are the answer rather than the failure. An `atomic` batch that does not apply is a trust-task-error carrying vta/app-state/put-many:atomicBatchRejected, whose details carry the same per-record outcomes.",
/// "type": "object",
/// "required": [
/// "mode",
/// "results"
/// ],
/// "properties": {
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// },
/// "highWatermark": {
/// "description": "The namespace's counter value after the batch. A writer that is also a sync consumer can adopt this instead of issuing a list call to discover where its own writes landed.",
/// "$ref": "#/definitions/Version"
/// },
/// "mode": {
/// "description": "The mode the maintainer applied, echoed so a caller relying on the default sees what it got.",
/// "type": "string",
/// "enum": [
/// "independent",
/// "atomic"
/// ]
/// },
/// "results": {
/// "description": "One result per requested write, in request order. Every write is accounted for.",
/// "type": "array",
/// "items": {
/// "$ref": "#/definitions/WriteResult"
/// },
/// "maxItems": 64,
/// "minItems": 1
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Response {
///Ecosystem-defined extension members per SPEC.md §4.5.1.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///The namespace's counter value after the batch. A writer that is also a sync consumer can adopt this instead of issuing a list call to discover where its own writes landed.
#[serde(
rename = "highWatermark",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub high_watermark: ::std::option::Option<Version>,
///The mode the maintainer applied, echoed so a caller relying on the default sees what it got.
pub mode: ResponseMode,
///One result per requested write, in request order. Every write is accounted for.
pub results: ::std::vec::Vec<WriteResult>,
}
///The mode the maintainer applied, echoed so a caller relying on the default sees what it got.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "The mode the maintainer applied, echoed so a caller relying on the default sees what it got.",
/// "type": "string",
/// "enum": [
/// "independent",
/// "atomic"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum ResponseMode {
#[serde(rename = "independent")]
Independent,
#[serde(rename = "atomic")]
Atomic,
}
impl ::std::fmt::Display for ResponseMode {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Independent => f.write_str("independent"),
Self::Atomic => f.write_str("atomic"),
}
}
}
impl ::std::str::FromStr for ResponseMode {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"independent" => Ok(Self::Independent),
"atomic" => Ok(Self::Atomic),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for ResponseMode {
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 ResponseMode {
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 ResponseMode {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///A value of the namespace's monotonic write counter (see this schema's description). Server-assigned; a producer never chooses one.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Version",
/// "description": "A value of the namespace's monotonic write counter (see this schema's description). Server-assigned; a producer never chooses one.",
/// "type": "integer",
/// "minimum": 1.0
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(transparent)]
pub struct Version(pub ::std::num::NonZeroU64);
impl ::std::ops::Deref for Version {
type Target = ::std::num::NonZeroU64;
fn deref(&self) -> &::std::num::NonZeroU64 {
&self.0
}
}
impl ::std::convert::From<Version> for ::std::num::NonZeroU64 {
fn from(value: Version) -> Self {
value.0
}
}
impl ::std::convert::From<::std::num::NonZeroU64> for Version {
fn from(value: ::std::num::NonZeroU64) -> Self {
Self(value)
}
}
impl ::std::str::FromStr for Version {
type Err = <::std::num::NonZeroU64 as ::std::str::FromStr>::Err;
fn from_str(value: &str) -> ::std::result::Result<Self, Self::Err> {
Ok(Self(value.parse()?))
}
}
impl ::std::convert::TryFrom<&str> for Version {
type Error = <::std::num::NonZeroU64 as ::std::str::FromStr>::Err;
fn try_from(value: &str) -> ::std::result::Result<Self, Self::Error> {
value.parse()
}
}
impl ::std::convert::TryFrom<String> for Version {
type Error = <::std::num::NonZeroU64 as ::std::str::FromStr>::Err;
fn try_from(value: String) -> ::std::result::Result<Self, Self::Error> {
value.parse()
}
}
impl ::std::fmt::Display for Version {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
self.0.fmt(f)
}
}
///One write within the batch. Shaped exactly like a vta/app-state/put payload minus the context and namespace, which the batch supplies.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Write",
/// "description": "One write within the batch. Shaped exactly like a vta/app-state/put payload minus the context and namespace, which the batch supplies.",
/// "type": "object",
/// "oneOf": [
/// {
/// "not": {
/// "required": [
/// "mergePatch"
/// ]
/// },
/// "required": [
/// "value"
/// ]
/// },
/// {
/// "not": {
/// "required": [
/// "value"
/// ]
/// },
/// "required": [
/// "mergePatch"
/// ]
/// }
/// ],
/// "required": [
/// "key"
/// ],
/// "properties": {
/// "expectedVersion": {
/// "description": "This write's own precondition, evaluated independently of every other write in the batch. A positive value requires the record to be at exactly that version; 0 requires that no live record exists.",
/// "$ref": "#/definitions/ExpectedVersion"
/// },
/// "key": {
/// "$ref": "#/definitions/Key"
/// },
/// "mergePatch": {
/// "description": "An RFC 7386 JSON Merge Patch applied to the record's current value. Requires a live record at the address; otherwise this write's outcome is `notFound`. Mutually exclusive with `value`.",
/// "type": "object"
/// },
/// "value": {
/// "description": "The complete new value, replacing whatever the record held. Any JSON value, including `null`. Mutually exclusive with `mergePatch`."
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(untagged, deny_unknown_fields)]
pub enum Write {
Variant0 {
///This write's own precondition, evaluated independently of every other write in the batch. A positive value requires the record to be at exactly that version; 0 requires that no live record exists.
#[serde(
rename = "expectedVersion",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
expected_version: ::std::option::Option<ExpectedVersion>,
key: Key,
///The complete new value, replacing whatever the record held. Any JSON value, including `null`. Mutually exclusive with `mergePatch`.
value: ::serde_json::Value,
},
Variant1 {
///This write's own precondition, evaluated independently of every other write in the batch. A positive value requires the record to be at exactly that version; 0 requires that no live record exists.
#[serde(
rename = "expectedVersion",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
expected_version: ::std::option::Option<ExpectedVersion>,
key: Key,
///An RFC 7386 JSON Merge Patch applied to the record's current value. Requires a live record at the address; otherwise this write's outcome is `notFound`. Mutually exclusive with `value`.
#[serde(rename = "mergePatch")]
merge_patch: ::serde_json::Map<::std::string::String, ::serde_json::Value>,
},
}
///The outcome of one write within a `vta/app-state/put-many` batch. Per-record rather than per-batch, because the default batch mode applies each write on its own merits: a caller flushing ten unrelated edits needs to know which one conflicted, not merely that something did.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "WriteResult",
/// "description": "The outcome of one write within a `vta/app-state/put-many` batch. Per-record rather than per-batch, because the default batch mode applies each write on its own merits: a caller flushing ten unrelated edits needs to know which one conflicted, not merely that something did.",
/// "type": "object",
/// "required": [
/// "key",
/// "outcome"
/// ],
/// "properties": {
/// "actualBytes": {
/// "description": "On `tooLarge`: the size of the rejected value in bytes.",
/// "type": "integer",
/// "minimum": 0.0
/// },
/// "created": {
/// "description": "On `written`: true when no live record existed at the address beforehand.",
/// "type": "boolean"
/// },
/// "currentDeleted": {
/// "description": "On `conflict`: true when the address holds a tombstone rather than a live record.",
/// "type": "boolean"
/// },
/// "currentValue": {
/// "description": "On `conflict`: the value the maintainer actually holds, returned WITH the rejection rather than left for the caller to re-read. A bare rejection has no fixed point under contention — between the rejection and the re-read the record can change again — so returning the winner's view removes the race rather than narrowing it. Absent when `currentDeleted` is true or no record exists."
/// },
/// "currentVersion": {
/// "description": "On `conflict`: the version the maintainer actually holds. Absent when the conflict is that no record exists (`expectedVersion` was positive and the address is empty).",
/// "$ref": "#/definitions/Version"
/// },
/// "key": {
/// "$ref": "#/definitions/Key"
/// },
/// "limitBytes": {
/// "description": "On `tooLarge`: the maintainer's per-record cap in bytes.",
/// "type": "integer",
/// "minimum": 0.0
/// },
/// "outcome": {
/// "description": "`written`: applied, and `version` carries the new value. `conflict`: `expectedVersion` did not match; `currentVersion`, `currentValue` and `currentDeleted` carry the maintainer's view so the caller can resolve without a re-read. `tooLarge`: the value exceeded the per-record cap; `limitBytes` and `actualBytes` say by how much. `notFound`: a `mergePatch` write named an address with no live record. `skipped`: atomic mode only — this write was not attempted because another in the batch failed.",
/// "type": "string",
/// "enum": [
/// "written",
/// "conflict",
/// "tooLarge",
/// "notFound",
/// "skipped"
/// ]
/// },
/// "version": {
/// "description": "The new version, on `written`.",
/// "$ref": "#/definitions/Version"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct WriteResult {
///On `tooLarge`: the size of the rejected value in bytes.
#[serde(
rename = "actualBytes",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub actual_bytes: ::std::option::Option<u64>,
///On `written`: true when no live record existed at the address beforehand.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub created: ::std::option::Option<bool>,
///On `conflict`: true when the address holds a tombstone rather than a live record.
#[serde(
rename = "currentDeleted",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub current_deleted: ::std::option::Option<bool>,
///On `conflict`: the value the maintainer actually holds, returned WITH the rejection rather than left for the caller to re-read. A bare rejection has no fixed point under contention — between the rejection and the re-read the record can change again — so returning the winner's view removes the race rather than narrowing it. Absent when `currentDeleted` is true or no record exists.
#[serde(
rename = "currentValue",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub current_value: ::std::option::Option<::serde_json::Value>,
///On `conflict`: the version the maintainer actually holds. Absent when the conflict is that no record exists (`expectedVersion` was positive and the address is empty).
#[serde(
rename = "currentVersion",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub current_version: ::std::option::Option<Version>,
pub key: Key,
///On `tooLarge`: the maintainer's per-record cap in bytes.
#[serde(
rename = "limitBytes",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub limit_bytes: ::std::option::Option<u64>,
///`written`: applied, and `version` carries the new value. `conflict`: `expectedVersion` did not match; `currentVersion`, `currentValue` and `currentDeleted` carry the maintainer's view so the caller can resolve without a re-read. `tooLarge`: the value exceeded the per-record cap; `limitBytes` and `actualBytes` say by how much. `notFound`: a `mergePatch` write named an address with no live record. `skipped`: atomic mode only — this write was not attempted because another in the batch failed.
pub outcome: WriteResultOutcome,
///The new version, on `written`.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub version: ::std::option::Option<Version>,
}
///`written`: applied, and `version` carries the new value. `conflict`: `expectedVersion` did not match; `currentVersion`, `currentValue` and `currentDeleted` carry the maintainer's view so the caller can resolve without a re-read. `tooLarge`: the value exceeded the per-record cap; `limitBytes` and `actualBytes` say by how much. `notFound`: a `mergePatch` write named an address with no live record. `skipped`: atomic mode only — this write was not attempted because another in the batch failed.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "`written`: applied, and `version` carries the new value. `conflict`: `expectedVersion` did not match; `currentVersion`, `currentValue` and `currentDeleted` carry the maintainer's view so the caller can resolve without a re-read. `tooLarge`: the value exceeded the per-record cap; `limitBytes` and `actualBytes` say by how much. `notFound`: a `mergePatch` write named an address with no live record. `skipped`: atomic mode only — this write was not attempted because another in the batch failed.",
/// "type": "string",
/// "enum": [
/// "written",
/// "conflict",
/// "tooLarge",
/// "notFound",
/// "skipped"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum WriteResultOutcome {
#[serde(rename = "written")]
Written,
#[serde(rename = "conflict")]
Conflict,
#[serde(rename = "tooLarge")]
TooLarge,
#[serde(rename = "notFound")]
NotFound,
#[serde(rename = "skipped")]
Skipped,
}
impl ::std::fmt::Display for WriteResultOutcome {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Written => f.write_str("written"),
Self::Conflict => f.write_str("conflict"),
Self::TooLarge => f.write_str("tooLarge"),
Self::NotFound => f.write_str("notFound"),
Self::Skipped => f.write_str("skipped"),
}
}
}
impl ::std::str::FromStr for WriteResultOutcome {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"written" => Ok(Self::Written),
"conflict" => Ok(Self::Conflict),
"tooLarge" => Ok(Self::TooLarge),
"notFound" => Ok(Self::NotFound),
"skipped" => Ok(Self::Skipped),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for WriteResultOutcome {
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 WriteResultOutcome {
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 WriteResultOutcome {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
/// Generation of default values for serde.
pub mod defaults {
pub(super) fn payload_mode() -> super::PayloadMode {
super::PayloadMode::Independent
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/vta/app-state/put-many/1.0";
const IS_PROOF_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"ExpectedVersion\": {\n \"description\": \"Optimistic-concurrency precondition on a write. A positive value requires that the record's current `version` equals it exactly. Zero means \\\"create only\\\" — the write applies only if no LIVE record exists at the address, which is what makes lease acquisition safe: without it two instances can each read \\\"absent\\\", each write, and each believe it won. A tombstone is not a live record, so `expectedVersion: 0` succeeds over one; the created record takes the namespace's next counter value, which is necessarily greater than the tombstone's.\",\n \"minimum\": 0,\n \"title\": \"ExpectedVersion\",\n \"type\": \"integer\"\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 \"Key\": {\n \"description\": \"Application-chosen identifier for a record within a namespace. Opaque to the maintainer: it MUST NOT be parsed, normalized, or case-folded, and prefix matching in `list` is a byte-prefix comparison over the UTF-8 encoding. Applications SHOULD use `/`-delimited hierarchical keys (`community/acme`, `contact/z6Mk…`) so that `prefix` can address a record family, but the delimiter is a convention between an application and itself — the maintainer attaches no meaning to it.\",\n \"maxLength\": 512,\n \"minLength\": 1,\n \"pattern\": \"^[^\\\\u0000]+$\",\n \"title\": \"Key\",\n \"type\": \"string\"\n },\n \"Namespace\": {\n \"description\": \"Scopes one application's records within a context, so several tools can share a context without colliding — `openvtc`, `cnm`, an agent runtime. The maintainer MUST NOT interpret the value; it is an opaque partition name. Namespaces are first-come and unreserved, so an application SHOULD pick a stable, specific one: a future per-namespace ACL would grant on this exact string, which makes renaming a namespace a migration rather than an edit.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"pattern\": \"^[a-z][a-z0-9]*(-[a-z0-9]+)*$\",\n \"title\": \"Namespace\",\n \"type\": \"string\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"Success response to vta/app-state/put-many in `independent` mode. Type https://trusttasks.org/spec/vta/app-state/put-many/1.0#response. A response is returned even when some writes conflicted, because the task did what it promised — applied each write on its own merits — and the per-record outcomes are the answer rather than the failure. An `atomic` batch that does not apply is a trust-task-error carrying vta/app-state/put-many:atomicBatchRejected, whose details carry the same per-record outcomes.\",\n \"properties\": {\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n },\n \"highWatermark\": {\n \"$ref\": \"#/$defs/Version\",\n \"description\": \"The namespace's counter value after the batch. A writer that is also a sync consumer can adopt this instead of issuing a list call to discover where its own writes landed.\"\n },\n \"mode\": {\n \"description\": \"The mode the maintainer applied, echoed so a caller relying on the default sees what it got.\",\n \"enum\": [\n \"independent\",\n \"atomic\"\n ],\n \"type\": \"string\"\n },\n \"results\": {\n \"description\": \"One result per requested write, in request order. Every write is accounted for.\",\n \"items\": {\n \"$ref\": \"#/$defs/WriteResult\"\n },\n \"maxItems\": 64,\n \"minItems\": 1,\n \"type\": \"array\"\n }\n },\n \"required\": [\n \"mode\",\n \"results\"\n ],\n \"title\": \"VTA Application State Put-Many — response payload\",\n \"type\": \"object\"\n },\n \"Version\": {\n \"description\": \"A value of the namespace's monotonic write counter (see this schema's description). Server-assigned; a producer never chooses one.\",\n \"minimum\": 1,\n \"title\": \"Version\",\n \"type\": \"integer\"\n },\n \"Write\": {\n \"additionalProperties\": false,\n \"description\": \"One write within the batch. Shaped exactly like a vta/app-state/put payload minus the context and namespace, which the batch supplies.\",\n \"oneOf\": [\n {\n \"not\": {\n \"required\": [\n \"mergePatch\"\n ]\n },\n \"required\": [\n \"value\"\n ]\n },\n {\n \"not\": {\n \"required\": [\n \"value\"\n ]\n },\n \"required\": [\n \"mergePatch\"\n ]\n }\n ],\n \"properties\": {\n \"expectedVersion\": {\n \"$ref\": \"#/$defs/ExpectedVersion\",\n \"description\": \"This write's own precondition, evaluated independently of every other write in the batch. A positive value requires the record to be at exactly that version; 0 requires that no live record exists.\"\n },\n \"key\": {\n \"$ref\": \"#/$defs/Key\"\n },\n \"mergePatch\": {\n \"description\": \"An RFC 7386 JSON Merge Patch applied to the record's current value. Requires a live record at the address; otherwise this write's outcome is `notFound`. Mutually exclusive with `value`.\",\n \"type\": \"object\"\n },\n \"value\": {\n \"description\": \"The complete new value, replacing whatever the record held. Any JSON value, including `null`. Mutually exclusive with `mergePatch`.\"\n }\n },\n \"required\": [\n \"key\"\n ],\n \"title\": \"Write\",\n \"type\": \"object\"\n },\n \"WriteResult\": {\n \"additionalProperties\": false,\n \"description\": \"The outcome of one write within a `vta/app-state/put-many` batch. Per-record rather than per-batch, because the default batch mode applies each write on its own merits: a caller flushing ten unrelated edits needs to know which one conflicted, not merely that something did.\",\n \"properties\": {\n \"actualBytes\": {\n \"description\": \"On `tooLarge`: the size of the rejected value in bytes.\",\n \"minimum\": 0,\n \"type\": \"integer\"\n },\n \"created\": {\n \"description\": \"On `written`: true when no live record existed at the address beforehand.\",\n \"type\": \"boolean\"\n },\n \"currentDeleted\": {\n \"description\": \"On `conflict`: true when the address holds a tombstone rather than a live record.\",\n \"type\": \"boolean\"\n },\n \"currentValue\": {\n \"description\": \"On `conflict`: the value the maintainer actually holds, returned WITH the rejection rather than left for the caller to re-read. A bare rejection has no fixed point under contention — between the rejection and the re-read the record can change again — so returning the winner's view removes the race rather than narrowing it. Absent when `currentDeleted` is true or no record exists.\"\n },\n \"currentVersion\": {\n \"$ref\": \"#/$defs/Version\",\n \"description\": \"On `conflict`: the version the maintainer actually holds. Absent when the conflict is that no record exists (`expectedVersion` was positive and the address is empty).\"\n },\n \"key\": {\n \"$ref\": \"#/$defs/Key\"\n },\n \"limitBytes\": {\n \"description\": \"On `tooLarge`: the maintainer's per-record cap in bytes.\",\n \"minimum\": 0,\n \"type\": \"integer\"\n },\n \"outcome\": {\n \"description\": \"`written`: applied, and `version` carries the new value. `conflict`: `expectedVersion` did not match; `currentVersion`, `currentValue` and `currentDeleted` carry the maintainer's view so the caller can resolve without a re-read. `tooLarge`: the value exceeded the per-record cap; `limitBytes` and `actualBytes` say by how much. `notFound`: a `mergePatch` write named an address with no live record. `skipped`: atomic mode only — this write was not attempted because another in the batch failed.\",\n \"enum\": [\n \"written\",\n \"conflict\",\n \"tooLarge\",\n \"notFound\",\n \"skipped\"\n ],\n \"type\": \"string\"\n },\n \"version\": {\n \"$ref\": \"#/$defs/Version\",\n \"description\": \"The new version, on `written`.\"\n }\n },\n \"required\": [\n \"key\",\n \"outcome\"\n ],\n \"title\": \"WriteResult\",\n \"type\": \"object\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/vta/app-state/put-many/1.0\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"Write up to 64 application-state records in one round trip, each carrying its own optimistic-concurrency precondition. The batch `mode` decides what a single failure costs: `independent` (the default) applies each write on its own merits, `atomic` applies all or none.\",\n \"properties\": {\n \"contextId\": {\n \"description\": \"The VTA context the records are scoped to; the isolation boundary.\",\n \"minLength\": 1,\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n },\n \"mode\": {\n \"$comment\": \"The default is load-bearing rather than a convenience; see the spec's Abstract.\",\n \"default\": \"independent\",\n \"description\": \"`independent` applies each write on its own merits, so one conflicted record does not block the other nine — what a flush of unrelated edits wants, and why it is the default. `atomic` applies all or none, for records carrying a joint invariant. An atomic DEFAULT would let one stale record silently wedge an entire flush, and a caller could not tell a wedged flush from a slow one.\",\n \"enum\": [\n \"independent\",\n \"atomic\"\n ],\n \"type\": \"string\"\n },\n \"namespace\": {\n \"$ref\": \"#/$defs/Namespace\",\n \"description\": \"One namespace per batch. Atomicity is only meaningful within the counter the writes take their versions from, and that counter is per (contextId, namespace).\"\n },\n \"writes\": {\n \"description\": \"The writes to apply. Keys MUST be distinct: two writes to one key in a batch have no defined order and are refused rather than serialised.\",\n \"items\": {\n \"$ref\": \"#/$defs/Write\"\n },\n \"maxItems\": 64,\n \"minItems\": 1,\n \"type\": \"array\"\n }\n },\n \"required\": [\n \"contextId\",\n \"namespace\",\n \"writes\"\n ],\n \"title\": \"VTA Application State Put-Many — payload\",\n \"type\": \"object\"\n}\n",
);
}
impl crate::Payload for Response {
const TYPE_URI: &'static str =
"https://trusttasks.org/spec/vta/app-state/put-many/1.0#response";
const IS_PROOF_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"ExpectedVersion\": {\n \"description\": \"Optimistic-concurrency precondition on a write. A positive value requires that the record's current `version` equals it exactly. Zero means \\\"create only\\\" — the write applies only if no LIVE record exists at the address, which is what makes lease acquisition safe: without it two instances can each read \\\"absent\\\", each write, and each believe it won. A tombstone is not a live record, so `expectedVersion: 0` succeeds over one; the created record takes the namespace's next counter value, which is necessarily greater than the tombstone's.\",\n \"minimum\": 0,\n \"title\": \"ExpectedVersion\",\n \"type\": \"integer\"\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 \"Key\": {\n \"description\": \"Application-chosen identifier for a record within a namespace. Opaque to the maintainer: it MUST NOT be parsed, normalized, or case-folded, and prefix matching in `list` is a byte-prefix comparison over the UTF-8 encoding. Applications SHOULD use `/`-delimited hierarchical keys (`community/acme`, `contact/z6Mk…`) so that `prefix` can address a record family, but the delimiter is a convention between an application and itself — the maintainer attaches no meaning to it.\",\n \"maxLength\": 512,\n \"minLength\": 1,\n \"pattern\": \"^[^\\\\u0000]+$\",\n \"title\": \"Key\",\n \"type\": \"string\"\n },\n \"Namespace\": {\n \"description\": \"Scopes one application's records within a context, so several tools can share a context without colliding — `openvtc`, `cnm`, an agent runtime. The maintainer MUST NOT interpret the value; it is an opaque partition name. Namespaces are first-come and unreserved, so an application SHOULD pick a stable, specific one: a future per-namespace ACL would grant on this exact string, which makes renaming a namespace a migration rather than an edit.\",\n \"maxLength\": 64,\n \"minLength\": 1,\n \"pattern\": \"^[a-z][a-z0-9]*(-[a-z0-9]+)*$\",\n \"title\": \"Namespace\",\n \"type\": \"string\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"Success response to vta/app-state/put-many in `independent` mode. Type https://trusttasks.org/spec/vta/app-state/put-many/1.0#response. A response is returned even when some writes conflicted, because the task did what it promised — applied each write on its own merits — and the per-record outcomes are the answer rather than the failure. An `atomic` batch that does not apply is a trust-task-error carrying vta/app-state/put-many:atomicBatchRejected, whose details carry the same per-record outcomes.\",\n \"properties\": {\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n },\n \"highWatermark\": {\n \"$ref\": \"#/$defs/Version\",\n \"description\": \"The namespace's counter value after the batch. A writer that is also a sync consumer can adopt this instead of issuing a list call to discover where its own writes landed.\"\n },\n \"mode\": {\n \"description\": \"The mode the maintainer applied, echoed so a caller relying on the default sees what it got.\",\n \"enum\": [\n \"independent\",\n \"atomic\"\n ],\n \"type\": \"string\"\n },\n \"results\": {\n \"description\": \"One result per requested write, in request order. Every write is accounted for.\",\n \"items\": {\n \"$ref\": \"#/$defs/WriteResult\"\n },\n \"maxItems\": 64,\n \"minItems\": 1,\n \"type\": \"array\"\n }\n },\n \"required\": [\n \"mode\",\n \"results\"\n ],\n \"title\": \"VTA Application State Put-Many — response payload\",\n \"type\": \"object\"\n },\n \"Version\": {\n \"description\": \"A value of the namespace's monotonic write counter (see this schema's description). Server-assigned; a producer never chooses one.\",\n \"minimum\": 1,\n \"title\": \"Version\",\n \"type\": \"integer\"\n },\n \"Write\": {\n \"additionalProperties\": false,\n \"description\": \"One write within the batch. Shaped exactly like a vta/app-state/put payload minus the context and namespace, which the batch supplies.\",\n \"oneOf\": [\n {\n \"not\": {\n \"required\": [\n \"mergePatch\"\n ]\n },\n \"required\": [\n \"value\"\n ]\n },\n {\n \"not\": {\n \"required\": [\n \"value\"\n ]\n },\n \"required\": [\n \"mergePatch\"\n ]\n }\n ],\n \"properties\": {\n \"expectedVersion\": {\n \"$ref\": \"#/$defs/ExpectedVersion\",\n \"description\": \"This write's own precondition, evaluated independently of every other write in the batch. A positive value requires the record to be at exactly that version; 0 requires that no live record exists.\"\n },\n \"key\": {\n \"$ref\": \"#/$defs/Key\"\n },\n \"mergePatch\": {\n \"description\": \"An RFC 7386 JSON Merge Patch applied to the record's current value. Requires a live record at the address; otherwise this write's outcome is `notFound`. Mutually exclusive with `value`.\",\n \"type\": \"object\"\n },\n \"value\": {\n \"description\": \"The complete new value, replacing whatever the record held. Any JSON value, including `null`. Mutually exclusive with `mergePatch`.\"\n }\n },\n \"required\": [\n \"key\"\n ],\n \"title\": \"Write\",\n \"type\": \"object\"\n },\n \"WriteResult\": {\n \"additionalProperties\": false,\n \"description\": \"The outcome of one write within a `vta/app-state/put-many` batch. Per-record rather than per-batch, because the default batch mode applies each write on its own merits: a caller flushing ten unrelated edits needs to know which one conflicted, not merely that something did.\",\n \"properties\": {\n \"actualBytes\": {\n \"description\": \"On `tooLarge`: the size of the rejected value in bytes.\",\n \"minimum\": 0,\n \"type\": \"integer\"\n },\n \"created\": {\n \"description\": \"On `written`: true when no live record existed at the address beforehand.\",\n \"type\": \"boolean\"\n },\n \"currentDeleted\": {\n \"description\": \"On `conflict`: true when the address holds a tombstone rather than a live record.\",\n \"type\": \"boolean\"\n },\n \"currentValue\": {\n \"description\": \"On `conflict`: the value the maintainer actually holds, returned WITH the rejection rather than left for the caller to re-read. A bare rejection has no fixed point under contention — between the rejection and the re-read the record can change again — so returning the winner's view removes the race rather than narrowing it. Absent when `currentDeleted` is true or no record exists.\"\n },\n \"currentVersion\": {\n \"$ref\": \"#/$defs/Version\",\n \"description\": \"On `conflict`: the version the maintainer actually holds. Absent when the conflict is that no record exists (`expectedVersion` was positive and the address is empty).\"\n },\n \"key\": {\n \"$ref\": \"#/$defs/Key\"\n },\n \"limitBytes\": {\n \"description\": \"On `tooLarge`: the maintainer's per-record cap in bytes.\",\n \"minimum\": 0,\n \"type\": \"integer\"\n },\n \"outcome\": {\n \"description\": \"`written`: applied, and `version` carries the new value. `conflict`: `expectedVersion` did not match; `currentVersion`, `currentValue` and `currentDeleted` carry the maintainer's view so the caller can resolve without a re-read. `tooLarge`: the value exceeded the per-record cap; `limitBytes` and `actualBytes` say by how much. `notFound`: a `mergePatch` write named an address with no live record. `skipped`: atomic mode only — this write was not attempted because another in the batch failed.\",\n \"enum\": [\n \"written\",\n \"conflict\",\n \"tooLarge\",\n \"notFound\",\n \"skipped\"\n ],\n \"type\": \"string\"\n },\n \"version\": {\n \"$ref\": \"#/$defs/Version\",\n \"description\": \"The new version, on `written`.\"\n }\n },\n \"required\": [\n \"key\",\n \"outcome\"\n ],\n \"title\": \"WriteResult\",\n \"type\": \"object\"\n }\n },\n \"$ref\": \"#/$defs/Response\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\n}\n",
);
}
#[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).
#[test]
fn request_example_1() {
const JSON: &str = "{\n \"id\": \"f7193a51-c960-4bf2-a4c6-ea0b2447c93d\",\n \"type\": \"https://trusttasks.org/spec/vta/app-state/put-many/1.0\",\n \"issuer\": \"did:key:z6MkOpenVtcClient\",\n \"recipient\": \"did:web:vta.example\",\n \"issuedAt\": \"2026-08-22T14:00:00Z\",\n \"payload\": {\n \"contextId\": \"personal\",\n \"namespace\": \"openvtc\",\n \"mode\": \"independent\",\n \"writes\": [\n {\n \"key\": \"community/acme\",\n \"expectedVersion\": 52,\n \"mergePatch\": { \"role\": \"owner\" }\n },\n {\n \"key\": \"community/borealis\",\n \"expectedVersion\": 31,\n \"mergePatch\": { \"label\": \"Borealis Collective (archived)\" }\n },\n {\n \"key\": \"profile/labels\",\n \"expectedVersion\": 0,\n \"value\": { \"colours\": { \"acme\": \"blue\", \"borealis\": \"green\" } }\n }\n ]\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2026-08-22T14:00:00Z\",\n \"verificationMethod\": \"did:key:z6MkOpenVtcClient#z6MkOpenVtcClient\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"z3FXQ...\"\n }\n}\n";
let doc: crate::TrustTask<super::Payload> =
serde_json::from_str(JSON).expect("deserialize request example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "request example failed round-trip");
}
#[test]
fn request_example_2() {
const JSON: &str = "{\n \"id\": \"082a4b62-da71-4c03-b5d7-fb1c3558da4e\",\n \"type\": \"https://trusttasks.org/spec/vta/app-state/put-many/1.0\",\n \"issuer\": \"did:key:z6MkOpenVtcClient\",\n \"recipient\": \"did:web:vta.example\",\n \"issuedAt\": \"2026-08-22T14:05:00Z\",\n \"payload\": {\n \"contextId\": \"personal\",\n \"namespace\": \"openvtc\",\n \"mode\": \"atomic\",\n \"writes\": [\n {\n \"key\": \"community/cyprus\",\n \"expectedVersion\": 0,\n \"value\": { \"label\": \"Cyprus Working Group\", \"joinedAt\": \"2026-08-22T14:05:00Z\", \"role\": \"member\" }\n },\n {\n \"key\": \"index/communities\",\n \"expectedVersion\": 58,\n \"value\": { \"ids\": [\"acme\", \"borealis\", \"cyprus\"] }\n }\n ]\n },\n \"proof\": {\n \"type\": \"DataIntegrityProof\",\n \"cryptosuite\": \"eddsa-jcs-2022\",\n \"created\": \"2026-08-22T14:05:00Z\",\n \"verificationMethod\": \"did:key:z6MkOpenVtcClient#z6MkOpenVtcClient\",\n \"proofPurpose\": \"assertionMethod\",\n \"proofValue\": \"z3FXQ...\"\n }\n}\n";
let doc: crate::TrustTask<super::Payload> =
serde_json::from_str(JSON).expect("deserialize request example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "request example failed round-trip");
}
#[test]
fn response_example_1() {
const JSON: &str = "{\n \"id\": \"193b5c73-eb82-4d14-c6e8-0c2d4669eb5f\",\n \"type\": \"https://trusttasks.org/spec/vta/app-state/put-many/1.0#response\",\n \"issuer\": \"did:web:vta.example\",\n \"recipient\": \"did:key:z6MkOpenVtcClient\",\n \"issuedAt\": \"2026-08-22T14:00:01Z\",\n \"threadId\": \"f7193a51-c960-4bf2-a4c6-ea0b2447c93d\",\n \"payload\": {\n \"mode\": \"independent\",\n \"results\": [\n {\n \"key\": \"community/acme\",\n \"outcome\": \"written\",\n \"version\": 59,\n \"created\": false\n },\n {\n \"key\": \"community/borealis\",\n \"outcome\": \"conflict\",\n \"currentVersion\": 57,\n \"currentValue\": {\n \"label\": \"Borealis Collective\",\n \"joinedAt\": \"2026-05-19T07:20:00Z\",\n \"role\": \"admin\"\n }\n },\n {\n \"key\": \"profile/labels\",\n \"outcome\": \"written\",\n \"version\": 60,\n \"created\": true\n }\n ],\n \"highWatermark\": 60\n }\n}\n";
let doc: crate::TrustTask<super::Response> =
serde_json::from_str(JSON).expect("deserialize response example");
let rendered = serde_json::to_value(&doc).expect("re-serialize");
let expected: serde_json::Value = serde_json::from_str(JSON).expect("re-parse expected");
assert_eq!(rendered, expected, "response example failed round-trip");
}
}