//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `rooms/create`. Version: `0.2`.
#[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())
}
}
}
/**
How often this room intends to write an `EpochAnchor`. **A statement of intent, not a schedule anything enforces** — nothing in this family can make an owner anchor.
It is worth stating anyway, and the reason is the interesting part: it makes **silence legible**. A room that says `renewal` and has not anchored in ten epochs is telling a member something, and a member who did not know what to expect could not have noticed. A room that says `never` is telling them not to wait for one.
- `never` — no anchor is intended. Honest, and cheap: anchoring costs a witnessed update and a rotation of the room DID's update key each time.
- `renewal` — one anchor per epoch change, which is the cadence §9's lifecycle already moves at.
- `manual` — the owner anchors when they decide to. A member should draw no freshness expectation from this at all, which is exactly what it is for: it is the honest answer where there is no rule.
Deliberately not a duration. A room that promised "daily" would be making a claim its owner's availability cannot keep, and a member comparing against a clock would read an owner's holiday as a host's misbehaviour.*/
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "AnchorCadence",
/// "description": "How often this room intends to write an `EpochAnchor`. **A statement of intent, not a schedule anything enforces** — nothing in this family can make an owner anchor.\n\nIt is worth stating anyway, and the reason is the interesting part: it makes **silence legible**. A room that says `renewal` and has not anchored in ten epochs is telling a member something, and a member who did not know what to expect could not have noticed. A room that says `never` is telling them not to wait for one.\n\n - `never` — no anchor is intended. Honest, and cheap: anchoring costs a witnessed update and a rotation of the room DID's update key each time.\n - `renewal` — one anchor per epoch change, which is the cadence §9's lifecycle already moves at.\n - `manual` — the owner anchors when they decide to. A member should draw no freshness expectation from this at all, which is exactly what it is for: it is the honest answer where there is no rule.\n\nDeliberately not a duration. A room that promised \"daily\" would be making a claim its owner's availability cannot keep, and a member comparing against a clock would read an owner's holiday as a host's misbehaviour.",
/// "type": "string",
/// "enum": [
/// "never",
/// "renewal",
/// "manual"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum AnchorCadence {
#[serde(rename = "never")]
Never,
#[serde(rename = "renewal")]
Renewal,
#[serde(rename = "manual")]
Manual,
}
impl ::std::fmt::Display for AnchorCadence {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Never => f.write_str("never"),
Self::Renewal => f.write_str("renewal"),
Self::Manual => f.write_str("manual"),
}
}
}
impl ::std::str::FromStr for AnchorCadence {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"never" => Ok(Self::Never),
"renewal" => Ok(Self::Renewal),
"manual" => Ok(Self::Manual),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for AnchorCadence {
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 AnchorCadence {
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 AnchorCadence {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///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())
})
}
}
///An owner registers a data room with a host. 0.2 adds `recordRetention`: how long the room keeps its records by age, stated at creation so that no record is ever removed under a rule its readers could not have read.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/rooms/create/0.2",
/// "title": "Payload",
/// "description": "An owner registers a data room with a host. 0.2 adds `recordRetention`: how long the room keeps its records by age, stated at creation so that no record is ever removed under a rule its readers could not have read.",
/// "type": "object",
/// "required": [
/// "ownerDid",
/// "roomId",
/// "visibility"
/// ],
/// "properties": {
/// "anchorCadence": {
/// "description": "What this room intends about anchoring. OPTIONAL; absent reads as `manual`, which draws no expectation and is the honest default for a room whose owner has not decided. A host stores it and serves it back — it constrains nothing the host does.",
/// "$ref": "#/definitions/AnchorCadence"
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1.",
/// "$ref": "#/definitions/Ext"
/// },
/// "ownerDid": {
/// "description": "The room's accountable party: the controller of the room's identifier, the issuer of every credential in it, and the party a host addresses about quota, abuse, or lifecycle. Visible at every visibility, including `private` — a room has someone answerable for it.",
/// "type": "string"
/// },
/// "recordRetention": {
/// "description": "How long the room keeps its records, by age: which records `rooms/records/prune` may remove. OPTIONAL; absent means no record is ever eligible by age. Unlike `retentionPolicy` it is not fixed: a holder of `admin` changes it with `rooms/records/retention/set`.",
/// "$ref": "#/definitions/RecordRetention"
/// },
/// "retentionDays": {
/// "description": "How long the host will hold the room after its epoch lapses without renewal, before storage may be reclaimed. Stated at creation rather than discovered: reclamation that surprises a member is a failure of the design, not of the member.",
/// "type": "integer",
/// "minimum": 1.0
/// },
/// "retentionPolicy": {
/// "description": "Whether this room keeps its history readable across a membership change. Absent means `chained`, which is the choice a room wants unless it has a reason not to: joining a room should mean being able to read it. A host MUST reject an epoch link for a room whose policy is `fromJoin` rather than storing one it declared it would not have.",
/// "$ref": "#/definitions/RetentionPolicy"
/// },
/// "roomId": {
/// "description": "The room's identifier, minted by its owner before this call. A host is told the identifier; it does not assign one, because a room whose identity the host chose could not move to another host.",
/// "type": "string"
/// },
/// "visibility": {
/// "$ref": "#/definitions/Visibility"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Payload {
///What this room intends about anchoring. OPTIONAL; absent reads as `manual`, which draws no expectation and is the honest default for a room whose owner has not decided. A host stores it and serves it back — it constrains nothing the host does.
#[serde(
rename = "anchorCadence",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub anchor_cadence: ::std::option::Option<AnchorCadence>,
///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 room's accountable party: the controller of the room's identifier, the issuer of every credential in it, and the party a host addresses about quota, abuse, or lifecycle. Visible at every visibility, including `private` — a room has someone answerable for it.
#[serde(rename = "ownerDid")]
pub owner_did: ::std::string::String,
///How long the room keeps its records, by age: which records `rooms/records/prune` may remove. OPTIONAL; absent means no record is ever eligible by age. Unlike `retentionPolicy` it is not fixed: a holder of `admin` changes it with `rooms/records/retention/set`.
#[serde(
rename = "recordRetention",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub record_retention: ::std::option::Option<RecordRetention>,
///How long the host will hold the room after its epoch lapses without renewal, before storage may be reclaimed. Stated at creation rather than discovered: reclamation that surprises a member is a failure of the design, not of the member.
#[serde(
rename = "retentionDays",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub retention_days: ::std::option::Option<::std::num::NonZeroU64>,
///Whether this room keeps its history readable across a membership change. Absent means `chained`, which is the choice a room wants unless it has a reason not to: joining a room should mean being able to read it. A host MUST reject an epoch link for a room whose policy is `fromJoin` rather than storing one it declared it would not have.
#[serde(
rename = "retentionPolicy",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub retention_policy: ::std::option::Option<RetentionPolicy>,
///The room's identifier, minted by its owner before this call. A host is told the identifier; it does not assign one, because a room whose identity the host chose could not move to another host.
#[serde(rename = "roomId")]
pub room_id: ::std::string::String,
pub visibility: Visibility,
}
impl Payload {
pub fn builder() -> builder::Payload {
Default::default()
}
}
/**
How long a room keeps its records, by age: the room's **record retention**. Records older than it states become **eligible** to be removed by `rooms/records/prune`; nothing is removed until a holder of `admin` asks for a prune, and nothing a prune removes can be brought back.
**Not `retentionPolicy`, and not `retentionDays`.** Both names are taken in this family and mean other things. `retentionPolicy` (`RetentionPolicy`) is whether a room's *key chain* lets a member read epochs from before they joined, fixed at creation because key material either exists for an epoch or it does not. `retentionDays` (`rooms/create`) is how long a host keeps a whole room after it lapses unrenewed. This is a third question — how long the room keeps its *records* while it is live — and unlike the first it is a decision a room revisits, so it is changeable (`rooms/records/retention/set`). A name sharing either's would invite a reader to apply one's rules to the other.
**Age is the host's to measure, so age is `updatedAt`.** On the sealed tiers a host can read nothing of a record but its key, version, status, pinning and when it last changed, so a policy the host applies can only be stated in those terms. That is also why it cannot use a record's own `validUntil`, which is sealed: expiry is a reader's rule, retention a host's. A curation is a change, so it restarts a record's age — a retraction starts a tombstone's.
**Absent, or `{}`, means no record is ever eligible by age**, and that is the reading a host MUST apply whenever it cannot read a policy: removing data under a rule nobody stated is the failure this member exists to prevent. Every member can read the policy in force (`rooms/info`), so a record is never removed under a rule its readers could not have known.*/
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "RecordRetention",
/// "description": "How long a room keeps its records, by age: the room's **record retention**. Records older than it states become **eligible** to be removed by `rooms/records/prune`; nothing is removed until a holder of `admin` asks for a prune, and nothing a prune removes can be brought back.\n\n**Not `retentionPolicy`, and not `retentionDays`.** Both names are taken in this family and mean other things. `retentionPolicy` (`RetentionPolicy`) is whether a room's *key chain* lets a member read epochs from before they joined, fixed at creation because key material either exists for an epoch or it does not. `retentionDays` (`rooms/create`) is how long a host keeps a whole room after it lapses unrenewed. This is a third question — how long the room keeps its *records* while it is live — and unlike the first it is a decision a room revisits, so it is changeable (`rooms/records/retention/set`). A name sharing either's would invite a reader to apply one's rules to the other.\n\n**Age is the host's to measure, so age is `updatedAt`.** On the sealed tiers a host can read nothing of a record but its key, version, status, pinning and when it last changed, so a policy the host applies can only be stated in those terms. That is also why it cannot use a record's own `validUntil`, which is sealed: expiry is a reader's rule, retention a host's. A curation is a change, so it restarts a record's age — a retraction starts a tombstone's.\n\n**Absent, or `{}`, means no record is ever eligible by age**, and that is the reading a host MUST apply whenever it cannot read a policy: removing data under a rule nobody stated is the failure this member exists to prevent. Every member can read the policy in force (`rooms/info`), so a record is never removed under a rule its readers could not have known.",
/// "type": "object",
/// "properties": {
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1. A host MUST NOT treat a member here as making more records eligible: a removal rule a member's reader cannot understand is one they were never told about.",
/// "$ref": "#/definitions/Ext"
/// },
/// "recordDays": {
/// "description": "Days a record is kept after it last changed, whatever its standing. A record — `active`, `deprecated` or `retracted` — whose `updatedAt` is more than this long ago is eligible, **unless it is pinned**: pinning says what matters in the room, and a room that wants a pinned record gone unpins it first. Absent means records are never eligible by age.",
/// "type": "integer",
/// "maximum": 36500.0,
/// "minimum": 1.0
/// },
/// "tombstoneDays": {
/// "description": "Days a retracted record's tombstone is kept after its retraction. A tombstone whose `updatedAt` is more than this long ago is eligible. Tombstones exist so that a member syncing incrementally learns of a retraction (`rooms/records/curate`); this is how long a member may stay away and still learn of one that way rather than by rebuilding their copy of the room. Absent means tombstones are never eligible.",
/// "type": "integer",
/// "maximum": 36500.0,
/// "minimum": 1.0
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct RecordRetention {
///Ecosystem-defined extension members per SPEC.md §4.5.1. A host MUST NOT treat a member here as making more records eligible: a removal rule a member's reader cannot understand is one they were never told about.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Days a record is kept after it last changed, whatever its standing. A record — `active`, `deprecated` or `retracted` — whose `updatedAt` is more than this long ago is eligible, **unless it is pinned**: pinning says what matters in the room, and a room that wants a pinned record gone unpins it first. Absent means records are never eligible by age.
#[serde(
rename = "recordDays",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub record_days: ::std::option::Option<::std::num::NonZeroU64>,
///Days a retracted record's tombstone is kept after its retraction. A tombstone whose `updatedAt` is more than this long ago is eligible. Tombstones exist so that a member syncing incrementally learns of a retraction (`rooms/records/curate`); this is how long a member may stay away and still learn of one that way rather than by rebuilding their copy of the room. Absent means tombstones are never eligible.
#[serde(
rename = "tombstoneDays",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub tombstone_days: ::std::option::Option<::std::num::NonZeroU64>,
}
impl ::std::default::Default for RecordRetention {
fn default() -> Self {
Self {
ext: Default::default(),
record_days: Default::default(),
tombstone_days: Default::default(),
}
}
}
impl RecordRetention {
pub fn builder() -> builder::RecordRetention {
Default::default()
}
}
///Success response to rooms/create. Type https://trusttasks.org/spec/rooms/create/0.2#response.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "description": "Success response to rooms/create. Type https://trusttasks.org/spec/rooms/create/0.2#response.",
/// "type": "object",
/// "required": [
/// "epoch",
/// "roomId"
/// ],
/// "properties": {
/// "anchorCadence": {
/// "description": "Echoed so a caller knows what was recorded rather than what was asked — the two differ on a host that predates this member.",
/// "$ref": "#/definitions/AnchorCadence"
/// },
/// "epoch": {
/// "description": "Always 1 for a new room.",
/// "type": "integer",
/// "minimum": 1.0
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "recordRetention": {
/// "description": "The record retention the host recorded, echoed for the same reason `anchorCadence` is: a caller learns what is in force rather than what it asked for. Absent when none was recorded.",
/// "$ref": "#/definitions/RecordRetention"
/// },
/// "roomId": {
/// "type": "string"
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct Response {
///Echoed so a caller knows what was recorded rather than what was asked — the two differ on a host that predates this member.
#[serde(
rename = "anchorCadence",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub anchor_cadence: ::std::option::Option<AnchorCadence>,
///Always 1 for a new room.
pub epoch: ::std::num::NonZeroU64,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///The record retention the host recorded, echoed for the same reason `anchorCadence` is: a caller learns what is in force rather than what it asked for. Absent when none was recorded.
#[serde(
rename = "recordRetention",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub record_retention: ::std::option::Option<RecordRetention>,
#[serde(rename = "roomId")]
pub room_id: ::std::string::String,
}
impl Response {
pub fn builder() -> builder::Response {
Default::default()
}
}
///Whether a room keeps its history readable across a membership change, fixed at creation and immutable thereafter — like `Visibility`, and for the same reason: the rungs of an epoch key chain either exist for an epoch or they do not, and no later change of mind can seal key material that was never sealed or unseal what was already severed. `chained`: each advance produces an `EpochLink`, so every member reads the room's whole retained history however long they have been in it — what a **library** wants, at the cost of post-compromise security for record content, since a compromised current key then reaches every retained epoch. `fromJoin`: no rungs are produced, so a member reads only from the epoch their group state is at — what a **stream** wants, and what a room under a strict forward-secrecy obligation wants, at the cost that a joining member finds an empty-looking room and nobody can reread a record once their group state has moved past the epoch it was sealed under. Absent means `chained`; see the prose on why the absent case is the readable one.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "RetentionPolicy",
/// "description": "Whether a room keeps its history readable across a membership change, fixed at creation and immutable thereafter — like `Visibility`, and for the same reason: the rungs of an epoch key chain either exist for an epoch or they do not, and no later change of mind can seal key material that was never sealed or unseal what was already severed. `chained`: each advance produces an `EpochLink`, so every member reads the room's whole retained history however long they have been in it — what a **library** wants, at the cost of post-compromise security for record content, since a compromised current key then reaches every retained epoch. `fromJoin`: no rungs are produced, so a member reads only from the epoch their group state is at — what a **stream** wants, and what a room under a strict forward-secrecy obligation wants, at the cost that a joining member finds an empty-looking room and nobody can reread a record once their group state has moved past the epoch it was sealed under. Absent means `chained`; see the prose on why the absent case is the readable one.",
/// "type": "string",
/// "enum": [
/// "chained",
/// "fromJoin"
/// ],
/// "$comment": "Deliberately no JSON Schema `default`. A declared default is materialised by the generated bindings — the member becomes non-optional with a serde default — so an absent value would reappear as an explicit one on re-serialisation and break round-trip idempotence for every document written before this member existed. The meaning of absence is stated in prose, where a binding cannot act on it."
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum RetentionPolicy {
#[serde(rename = "chained")]
Chained,
#[serde(rename = "fromJoin")]
FromJoin,
}
impl ::std::fmt::Display for RetentionPolicy {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Chained => f.write_str("chained"),
Self::FromJoin => f.write_str("fromJoin"),
}
}
}
impl ::std::str::FromStr for RetentionPolicy {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"chained" => Ok(Self::Chained),
"fromJoin" => Ok(Self::FromJoin),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for RetentionPolicy {
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 RetentionPolicy {
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 RetentionPolicy {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///How much of a room its host can see, fixed at creation and immutable thereafter. `open`: records are cleartext, searchable and fully audited. `attributed`: record content is sealed, and the host still learns which member acted. `private`: content is sealed and membership is presented in zero knowledge, so the host verifies that a member acted without learning which. Immutable because a downgrade cannot un-see cleartext and an upgrade would protect only what came after while presenting as though it protected everything.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Visibility",
/// "description": "How much of a room its host can see, fixed at creation and immutable thereafter. `open`: records are cleartext, searchable and fully audited. `attributed`: record content is sealed, and the host still learns which member acted. `private`: content is sealed and membership is presented in zero knowledge, so the host verifies that a member acted without learning which. Immutable because a downgrade cannot un-see cleartext and an upgrade would protect only what came after while presenting as though it protected everything.",
/// "type": "string",
/// "enum": [
/// "open",
/// "attributed",
/// "private"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
#[non_exhaustive]
pub enum Visibility {
#[serde(rename = "open")]
Open,
#[serde(rename = "attributed")]
Attributed,
#[serde(rename = "private")]
Private,
}
impl ::std::fmt::Display for Visibility {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Open => f.write_str("open"),
Self::Attributed => f.write_str("attributed"),
Self::Private => f.write_str("private"),
}
}
}
impl ::std::str::FromStr for Visibility {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"open" => Ok(Self::Open),
"attributed" => Ok(Self::Attributed),
"private" => Ok(Self::Private),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for Visibility {
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 Visibility {
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 Visibility {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
/// Types for composing complex structures.
pub mod builder {
#[derive(Clone, Debug)]
pub struct Payload {
anchor_cadence: ::std::result::Result<
::std::option::Option<super::AnchorCadence>,
::std::string::String,
>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
owner_did: ::std::result::Result<::std::string::String, ::std::string::String>,
record_retention: ::std::result::Result<
::std::option::Option<super::RecordRetention>,
::std::string::String,
>,
retention_days: ::std::result::Result<
::std::option::Option<::std::num::NonZeroU64>,
::std::string::String,
>,
retention_policy: ::std::result::Result<
::std::option::Option<super::RetentionPolicy>,
::std::string::String,
>,
room_id: ::std::result::Result<::std::string::String, ::std::string::String>,
visibility: ::std::result::Result<super::Visibility, ::std::string::String>,
}
impl ::std::default::Default for Payload {
fn default() -> Self {
Self {
anchor_cadence: Ok(Default::default()),
ext: Ok(Default::default()),
owner_did: Err("no value supplied for owner_did".to_string()),
record_retention: Ok(Default::default()),
retention_days: Ok(Default::default()),
retention_policy: Ok(Default::default()),
room_id: Err("no value supplied for room_id".to_string()),
visibility: Err("no value supplied for visibility".to_string()),
}
}
}
impl Payload {
pub fn anchor_cadence<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::AnchorCadence>>,
T::Error: ::std::fmt::Display,
{
self.anchor_cadence = value
.try_into()
.map_err(|e| format!("error converting supplied value for anchor_cadence: {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 owner_did<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::string::String>,
T::Error: ::std::fmt::Display,
{
self.owner_did = value
.try_into()
.map_err(|e| format!("error converting supplied value for owner_did: {e}"));
self
}
pub fn record_retention<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::RecordRetention>>,
T::Error: ::std::fmt::Display,
{
self.record_retention = value
.try_into()
.map_err(|e| format!("error converting supplied value for record_retention: {e}"));
self
}
pub fn retention_days<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<::std::num::NonZeroU64>>,
T::Error: ::std::fmt::Display,
{
self.retention_days = value
.try_into()
.map_err(|e| format!("error converting supplied value for retention_days: {e}"));
self
}
pub fn retention_policy<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::RetentionPolicy>>,
T::Error: ::std::fmt::Display,
{
self.retention_policy = value
.try_into()
.map_err(|e| format!("error converting supplied value for retention_policy: {e}"));
self
}
pub fn room_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::string::String>,
T::Error: ::std::fmt::Display,
{
self.room_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for room_id: {e}"));
self
}
pub fn visibility<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<super::Visibility>,
T::Error: ::std::fmt::Display,
{
self.visibility = value
.try_into()
.map_err(|e| format!("error converting supplied value for visibility: {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 {
anchor_cadence: value.anchor_cadence?,
ext: value.ext?,
owner_did: value.owner_did?,
record_retention: value.record_retention?,
retention_days: value.retention_days?,
retention_policy: value.retention_policy?,
room_id: value.room_id?,
visibility: value.visibility?,
})
}
}
impl ::std::convert::From<super::Payload> for Payload {
fn from(value: super::Payload) -> Self {
Self {
anchor_cadence: Ok(value.anchor_cadence),
ext: Ok(value.ext),
owner_did: Ok(value.owner_did),
record_retention: Ok(value.record_retention),
retention_days: Ok(value.retention_days),
retention_policy: Ok(value.retention_policy),
room_id: Ok(value.room_id),
visibility: Ok(value.visibility),
}
}
}
#[derive(Clone, Debug)]
pub struct RecordRetention {
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
record_days: ::std::result::Result<
::std::option::Option<::std::num::NonZeroU64>,
::std::string::String,
>,
tombstone_days: ::std::result::Result<
::std::option::Option<::std::num::NonZeroU64>,
::std::string::String,
>,
}
impl ::std::default::Default for RecordRetention {
fn default() -> Self {
Self {
ext: Ok(Default::default()),
record_days: Ok(Default::default()),
tombstone_days: Ok(Default::default()),
}
}
}
impl RecordRetention {
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 record_days<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<::std::num::NonZeroU64>>,
T::Error: ::std::fmt::Display,
{
self.record_days = value
.try_into()
.map_err(|e| format!("error converting supplied value for record_days: {e}"));
self
}
pub fn tombstone_days<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<::std::num::NonZeroU64>>,
T::Error: ::std::fmt::Display,
{
self.tombstone_days = value
.try_into()
.map_err(|e| format!("error converting supplied value for tombstone_days: {e}"));
self
}
}
impl ::std::convert::TryFrom<RecordRetention> for super::RecordRetention {
type Error = super::error::ConversionError;
fn try_from(
value: RecordRetention,
) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
ext: value.ext?,
record_days: value.record_days?,
tombstone_days: value.tombstone_days?,
})
}
}
impl ::std::convert::From<super::RecordRetention> for RecordRetention {
fn from(value: super::RecordRetention) -> Self {
Self {
ext: Ok(value.ext),
record_days: Ok(value.record_days),
tombstone_days: Ok(value.tombstone_days),
}
}
}
#[derive(Clone, Debug)]
pub struct Response {
anchor_cadence: ::std::result::Result<
::std::option::Option<super::AnchorCadence>,
::std::string::String,
>,
epoch: ::std::result::Result<::std::num::NonZeroU64, ::std::string::String>,
ext: ::std::result::Result<::std::option::Option<super::Ext>, ::std::string::String>,
record_retention: ::std::result::Result<
::std::option::Option<super::RecordRetention>,
::std::string::String,
>,
room_id: ::std::result::Result<::std::string::String, ::std::string::String>,
}
impl ::std::default::Default for Response {
fn default() -> Self {
Self {
anchor_cadence: Ok(Default::default()),
epoch: Err("no value supplied for epoch".to_string()),
ext: Ok(Default::default()),
record_retention: Ok(Default::default()),
room_id: Err("no value supplied for room_id".to_string()),
}
}
}
impl Response {
pub fn anchor_cadence<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::AnchorCadence>>,
T::Error: ::std::fmt::Display,
{
self.anchor_cadence = value
.try_into()
.map_err(|e| format!("error converting supplied value for anchor_cadence: {e}"));
self
}
pub fn epoch<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::num::NonZeroU64>,
T::Error: ::std::fmt::Display,
{
self.epoch = value
.try_into()
.map_err(|e| format!("error converting supplied value for epoch: {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 record_retention<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::option::Option<super::RecordRetention>>,
T::Error: ::std::fmt::Display,
{
self.record_retention = value
.try_into()
.map_err(|e| format!("error converting supplied value for record_retention: {e}"));
self
}
pub fn room_id<T>(mut self, value: T) -> Self
where
T: ::std::convert::TryInto<::std::string::String>,
T::Error: ::std::fmt::Display,
{
self.room_id = value
.try_into()
.map_err(|e| format!("error converting supplied value for room_id: {e}"));
self
}
}
impl ::std::convert::TryFrom<Response> for super::Response {
type Error = super::error::ConversionError;
fn try_from(value: Response) -> ::std::result::Result<Self, super::error::ConversionError> {
Ok(Self {
anchor_cadence: value.anchor_cadence?,
epoch: value.epoch?,
ext: value.ext?,
record_retention: value.record_retention?,
room_id: value.room_id?,
})
}
}
impl ::std::convert::From<super::Response> for Response {
fn from(value: super::Response) -> Self {
Self {
anchor_cadence: Ok(value.anchor_cadence),
epoch: Ok(value.epoch),
ext: Ok(value.ext),
record_retention: Ok(value.record_retention),
room_id: Ok(value.room_id),
}
}
}
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/rooms/create/0.2";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"AnchorCadence\": {\n \"description\": \"How often this room intends to write an `EpochAnchor`. **A statement of intent, not a schedule anything enforces** — nothing in this family can make an owner anchor.\\n\\nIt is worth stating anyway, and the reason is the interesting part: it makes **silence legible**. A room that says `renewal` and has not anchored in ten epochs is telling a member something, and a member who did not know what to expect could not have noticed. A room that says `never` is telling them not to wait for one.\\n\\n - `never` — no anchor is intended. Honest, and cheap: anchoring costs a witnessed update and a rotation of the room DID's update key each time.\\n - `renewal` — one anchor per epoch change, which is the cadence §9's lifecycle already moves at.\\n - `manual` — the owner anchors when they decide to. A member should draw no freshness expectation from this at all, which is exactly what it is for: it is the honest answer where there is no rule.\\n\\nDeliberately not a duration. A room that promised \\\"daily\\\" would be making a claim its owner's availability cannot keep, and a member comparing against a clock would read an owner's holiday as a host's misbehaviour.\",\n \"enum\": [\n \"never\",\n \"renewal\",\n \"manual\"\n ],\n \"title\": \"AnchorCadence\",\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 \"RecordRetention\": {\n \"additionalProperties\": false,\n \"description\": \"How long a room keeps its records, by age: the room's **record retention**. Records older than it states become **eligible** to be removed by `rooms/records/prune`; nothing is removed until a holder of `admin` asks for a prune, and nothing a prune removes can be brought back.\\n\\n**Not `retentionPolicy`, and not `retentionDays`.** Both names are taken in this family and mean other things. `retentionPolicy` (`RetentionPolicy`) is whether a room's *key chain* lets a member read epochs from before they joined, fixed at creation because key material either exists for an epoch or it does not. `retentionDays` (`rooms/create`) is how long a host keeps a whole room after it lapses unrenewed. This is a third question — how long the room keeps its *records* while it is live — and unlike the first it is a decision a room revisits, so it is changeable (`rooms/records/retention/set`). A name sharing either's would invite a reader to apply one's rules to the other.\\n\\n**Age is the host's to measure, so age is `updatedAt`.** On the sealed tiers a host can read nothing of a record but its key, version, status, pinning and when it last changed, so a policy the host applies can only be stated in those terms. That is also why it cannot use a record's own `validUntil`, which is sealed: expiry is a reader's rule, retention a host's. A curation is a change, so it restarts a record's age — a retraction starts a tombstone's.\\n\\n**Absent, or `{}`, means no record is ever eligible by age**, and that is the reading a host MUST apply whenever it cannot read a policy: removing data under a rule nobody stated is the failure this member exists to prevent. Every member can read the policy in force (`rooms/info`), so a record is never removed under a rule its readers could not have known.\",\n \"properties\": {\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1. A host MUST NOT treat a member here as making more records eligible: a removal rule a member's reader cannot understand is one they were never told about.\"\n },\n \"recordDays\": {\n \"description\": \"Days a record is kept after it last changed, whatever its standing. A record — `active`, `deprecated` or `retracted` — whose `updatedAt` is more than this long ago is eligible, **unless it is pinned**: pinning says what matters in the room, and a room that wants a pinned record gone unpins it first. Absent means records are never eligible by age.\",\n \"maximum\": 36500,\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"tombstoneDays\": {\n \"description\": \"Days a retracted record's tombstone is kept after its retraction. A tombstone whose `updatedAt` is more than this long ago is eligible. Tombstones exist so that a member syncing incrementally learns of a retraction (`rooms/records/curate`); this is how long a member may stay away and still learn of one that way rather than by rebuilding their copy of the room. Absent means tombstones are never eligible.\",\n \"maximum\": 36500,\n \"minimum\": 1,\n \"type\": \"integer\"\n }\n },\n \"title\": \"RecordRetention\",\n \"type\": \"object\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"Success response to rooms/create. Type https://trusttasks.org/spec/rooms/create/0.2#response.\",\n \"properties\": {\n \"anchorCadence\": {\n \"$ref\": \"#/$defs/AnchorCadence\",\n \"description\": \"Echoed so a caller knows what was recorded rather than what was asked — the two differ on a host that predates this member.\"\n },\n \"epoch\": {\n \"description\": \"Always 1 for a new room.\",\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"recordRetention\": {\n \"$ref\": \"#/$defs/RecordRetention\",\n \"description\": \"The record retention the host recorded, echoed for the same reason `anchorCadence` is: a caller learns what is in force rather than what it asked for. Absent when none was recorded.\"\n },\n \"roomId\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"roomId\",\n \"epoch\"\n ],\n \"title\": \"Rooms Create — response payload\",\n \"type\": \"object\"\n },\n \"RetentionPolicy\": {\n \"$comment\": \"Deliberately no JSON Schema `default`. A declared default is materialised by the generated bindings — the member becomes non-optional with a serde default — so an absent value would reappear as an explicit one on re-serialisation and break round-trip idempotence for every document written before this member existed. The meaning of absence is stated in prose, where a binding cannot act on it.\",\n \"description\": \"Whether a room keeps its history readable across a membership change, fixed at creation and immutable thereafter — like `Visibility`, and for the same reason: the rungs of an epoch key chain either exist for an epoch or they do not, and no later change of mind can seal key material that was never sealed or unseal what was already severed. `chained`: each advance produces an `EpochLink`, so every member reads the room's whole retained history however long they have been in it — what a **library** wants, at the cost of post-compromise security for record content, since a compromised current key then reaches every retained epoch. `fromJoin`: no rungs are produced, so a member reads only from the epoch their group state is at — what a **stream** wants, and what a room under a strict forward-secrecy obligation wants, at the cost that a joining member finds an empty-looking room and nobody can reread a record once their group state has moved past the epoch it was sealed under. Absent means `chained`; see the prose on why the absent case is the readable one.\",\n \"enum\": [\n \"chained\",\n \"fromJoin\"\n ],\n \"title\": \"RetentionPolicy\",\n \"type\": \"string\"\n },\n \"Visibility\": {\n \"description\": \"How much of a room its host can see, fixed at creation and immutable thereafter. `open`: records are cleartext, searchable and fully audited. `attributed`: record content is sealed, and the host still learns which member acted. `private`: content is sealed and membership is presented in zero knowledge, so the host verifies that a member acted without learning which. Immutable because a downgrade cannot un-see cleartext and an upgrade would protect only what came after while presenting as though it protected everything.\",\n \"enum\": [\n \"open\",\n \"attributed\",\n \"private\"\n ],\n \"title\": \"Visibility\",\n \"type\": \"string\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/rooms/create/0.2\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"An owner registers a data room with a host. 0.2 adds `recordRetention`: how long the room keeps its records by age, stated at creation so that no record is ever removed under a rule its readers could not have read.\",\n \"properties\": {\n \"anchorCadence\": {\n \"$ref\": \"#/$defs/AnchorCadence\",\n \"description\": \"What this room intends about anchoring. OPTIONAL; absent reads as `manual`, which draws no expectation and is the honest default for a room whose owner has not decided. A host stores it and serves it back — it constrains nothing the host does.\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1.\"\n },\n \"ownerDid\": {\n \"description\": \"The room's accountable party: the controller of the room's identifier, the issuer of every credential in it, and the party a host addresses about quota, abuse, or lifecycle. Visible at every visibility, including `private` — a room has someone answerable for it.\",\n \"type\": \"string\"\n },\n \"recordRetention\": {\n \"$ref\": \"#/$defs/RecordRetention\",\n \"description\": \"How long the room keeps its records, by age: which records `rooms/records/prune` may remove. OPTIONAL; absent means no record is ever eligible by age. Unlike `retentionPolicy` it is not fixed: a holder of `admin` changes it with `rooms/records/retention/set`.\"\n },\n \"retentionDays\": {\n \"description\": \"How long the host will hold the room after its epoch lapses without renewal, before storage may be reclaimed. Stated at creation rather than discovered: reclamation that surprises a member is a failure of the design, not of the member.\",\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"retentionPolicy\": {\n \"$ref\": \"#/$defs/RetentionPolicy\",\n \"description\": \"Whether this room keeps its history readable across a membership change. Absent means `chained`, which is the choice a room wants unless it has a reason not to: joining a room should mean being able to read it. A host MUST reject an epoch link for a room whose policy is `fromJoin` rather than storing one it declared it would not have.\"\n },\n \"roomId\": {\n \"description\": \"The room's identifier, minted by its owner before this call. A host is told the identifier; it does not assign one, because a room whose identity the host chose could not move to another host.\",\n \"type\": \"string\"\n },\n \"visibility\": {\n \"$ref\": \"#/$defs/Visibility\"\n }\n },\n \"required\": [\n \"roomId\",\n \"visibility\",\n \"ownerDid\"\n ],\n \"title\": \"Rooms Create — payload\",\n \"type\": \"object\"\n}\n",
);
}
impl crate::Payload for Response {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/rooms/create/0.2#response";
const IS_PROOF_REQUIRED: bool = true;
const IS_ISSUED_AT_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
const PAYLOAD_SCHEMA: Option<&'static str> = Some(
"{\n \"$defs\": {\n \"AnchorCadence\": {\n \"description\": \"How often this room intends to write an `EpochAnchor`. **A statement of intent, not a schedule anything enforces** — nothing in this family can make an owner anchor.\\n\\nIt is worth stating anyway, and the reason is the interesting part: it makes **silence legible**. A room that says `renewal` and has not anchored in ten epochs is telling a member something, and a member who did not know what to expect could not have noticed. A room that says `never` is telling them not to wait for one.\\n\\n - `never` — no anchor is intended. Honest, and cheap: anchoring costs a witnessed update and a rotation of the room DID's update key each time.\\n - `renewal` — one anchor per epoch change, which is the cadence §9's lifecycle already moves at.\\n - `manual` — the owner anchors when they decide to. A member should draw no freshness expectation from this at all, which is exactly what it is for: it is the honest answer where there is no rule.\\n\\nDeliberately not a duration. A room that promised \\\"daily\\\" would be making a claim its owner's availability cannot keep, and a member comparing against a clock would read an owner's holiday as a host's misbehaviour.\",\n \"enum\": [\n \"never\",\n \"renewal\",\n \"manual\"\n ],\n \"title\": \"AnchorCadence\",\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 \"RecordRetention\": {\n \"additionalProperties\": false,\n \"description\": \"How long a room keeps its records, by age: the room's **record retention**. Records older than it states become **eligible** to be removed by `rooms/records/prune`; nothing is removed until a holder of `admin` asks for a prune, and nothing a prune removes can be brought back.\\n\\n**Not `retentionPolicy`, and not `retentionDays`.** Both names are taken in this family and mean other things. `retentionPolicy` (`RetentionPolicy`) is whether a room's *key chain* lets a member read epochs from before they joined, fixed at creation because key material either exists for an epoch or it does not. `retentionDays` (`rooms/create`) is how long a host keeps a whole room after it lapses unrenewed. This is a third question — how long the room keeps its *records* while it is live — and unlike the first it is a decision a room revisits, so it is changeable (`rooms/records/retention/set`). A name sharing either's would invite a reader to apply one's rules to the other.\\n\\n**Age is the host's to measure, so age is `updatedAt`.** On the sealed tiers a host can read nothing of a record but its key, version, status, pinning and when it last changed, so a policy the host applies can only be stated in those terms. That is also why it cannot use a record's own `validUntil`, which is sealed: expiry is a reader's rule, retention a host's. A curation is a change, so it restarts a record's age — a retraction starts a tombstone's.\\n\\n**Absent, or `{}`, means no record is ever eligible by age**, and that is the reading a host MUST apply whenever it cannot read a policy: removing data under a rule nobody stated is the failure this member exists to prevent. Every member can read the policy in force (`rooms/info`), so a record is never removed under a rule its readers could not have known.\",\n \"properties\": {\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1. A host MUST NOT treat a member here as making more records eligible: a removal rule a member's reader cannot understand is one they were never told about.\"\n },\n \"recordDays\": {\n \"description\": \"Days a record is kept after it last changed, whatever its standing. A record — `active`, `deprecated` or `retracted` — whose `updatedAt` is more than this long ago is eligible, **unless it is pinned**: pinning says what matters in the room, and a room that wants a pinned record gone unpins it first. Absent means records are never eligible by age.\",\n \"maximum\": 36500,\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"tombstoneDays\": {\n \"description\": \"Days a retracted record's tombstone is kept after its retraction. A tombstone whose `updatedAt` is more than this long ago is eligible. Tombstones exist so that a member syncing incrementally learns of a retraction (`rooms/records/curate`); this is how long a member may stay away and still learn of one that way rather than by rebuilding their copy of the room. Absent means tombstones are never eligible.\",\n \"maximum\": 36500,\n \"minimum\": 1,\n \"type\": \"integer\"\n }\n },\n \"title\": \"RecordRetention\",\n \"type\": \"object\"\n },\n \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"description\": \"Success response to rooms/create. Type https://trusttasks.org/spec/rooms/create/0.2#response.\",\n \"properties\": {\n \"anchorCadence\": {\n \"$ref\": \"#/$defs/AnchorCadence\",\n \"description\": \"Echoed so a caller knows what was recorded rather than what was asked — the two differ on a host that predates this member.\"\n },\n \"epoch\": {\n \"description\": \"Always 1 for a new room.\",\n \"minimum\": 1,\n \"type\": \"integer\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"recordRetention\": {\n \"$ref\": \"#/$defs/RecordRetention\",\n \"description\": \"The record retention the host recorded, echoed for the same reason `anchorCadence` is: a caller learns what is in force rather than what it asked for. Absent when none was recorded.\"\n },\n \"roomId\": {\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"roomId\",\n \"epoch\"\n ],\n \"title\": \"Rooms Create — response payload\",\n \"type\": \"object\"\n },\n \"RetentionPolicy\": {\n \"$comment\": \"Deliberately no JSON Schema `default`. A declared default is materialised by the generated bindings — the member becomes non-optional with a serde default — so an absent value would reappear as an explicit one on re-serialisation and break round-trip idempotence for every document written before this member existed. The meaning of absence is stated in prose, where a binding cannot act on it.\",\n \"description\": \"Whether a room keeps its history readable across a membership change, fixed at creation and immutable thereafter — like `Visibility`, and for the same reason: the rungs of an epoch key chain either exist for an epoch or they do not, and no later change of mind can seal key material that was never sealed or unseal what was already severed. `chained`: each advance produces an `EpochLink`, so every member reads the room's whole retained history however long they have been in it — what a **library** wants, at the cost of post-compromise security for record content, since a compromised current key then reaches every retained epoch. `fromJoin`: no rungs are produced, so a member reads only from the epoch their group state is at — what a **stream** wants, and what a room under a strict forward-secrecy obligation wants, at the cost that a joining member finds an empty-looking room and nobody can reread a record once their group state has moved past the epoch it was sealed under. Absent means `chained`; see the prose on why the absent case is the readable one.\",\n \"enum\": [\n \"chained\",\n \"fromJoin\"\n ],\n \"title\": \"RetentionPolicy\",\n \"type\": \"string\"\n },\n \"Visibility\": {\n \"description\": \"How much of a room its host can see, fixed at creation and immutable thereafter. `open`: records are cleartext, searchable and fully audited. `attributed`: record content is sealed, and the host still learns which member acted. `private`: content is sealed and membership is presented in zero knowledge, so the host verifies that a member acted without learning which. Immutable because a downgrade cannot un-see cleartext and an upgrade would protect only what came after while presenting as though it protected everything.\",\n \"enum\": [\n \"open\",\n \"attributed\",\n \"private\"\n ],\n \"title\": \"Visibility\",\n \"type\": \"string\"\n }\n },\n \"$ref\": \"#/$defs/Response\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\"\n}\n",
);
}
impl crate::RequestPayload for Payload {
type Response = Response;
}
/// The extended error codes this specification declares (SPEC §7.3 item 9,
/// §8.5), in declaration order. Empty when it declares none.
pub const ERROR_CODES: &[crate::DeclaredErrorCode] = &[
error_codes::ALREADY_EXISTS,
error_codes::VISIBILITY_NOT_PERMITTED,
];
/// One constant per extended error code this specification declares
/// (SPEC §7.3 item 9), named for its local part.
///
/// Emit these rather than a string literal: the code is read from the
/// specification, so it cannot name a code the specification never
/// declared.
pub mod error_codes {
/// `rooms/create:alreadyExists`
///
/// A room with that identifier is already registered with this host.
///
/// Declared `retryable: false`.
pub const ALREADY_EXISTS: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "rooms/create:alreadyExists",
retryable: false,
details_schema: ::core::option::Option::None,
};
/// `rooms/create:visibilityNotPermitted`
///
/// The host's governance does not permit rooms of this visibility.
///
/// Declared `retryable: false`.
pub const VISIBILITY_NOT_PERMITTED: crate::DeclaredErrorCode = crate::DeclaredErrorCode {
code: "rooms/create:visibilityNotPermitted",
retryable: false,
details_schema: ::core::option::Option::None,
};
}