//! Generated by `trust-tasks-codegen` — do not edit by hand.
//!
//! Spec slug: `acl/update`. Version: `0.1`.
#[allow(unused_imports)]
use serde::{Deserialize, Serialize};
/// Error types.
pub mod error {
/// Error from a `TryFrom` or `FromStr` implementation.
pub struct ConversionError(::std::borrow::Cow<'static, str>);
impl ::std::error::Error for ConversionError {}
impl ::std::fmt::Display for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Display::fmt(&self.0, f)
}
}
impl ::std::fmt::Debug for ConversionError {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
::std::fmt::Debug::fmt(&self.0, f)
}
}
impl From<&'static str> for ConversionError {
fn from(value: &'static str) -> Self {
Self(value.into())
}
}
impl From<String> for ConversionError {
fn from(value: String) -> Self {
Self(value.into())
}
}
}
///`AclEntry`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "AclEntry",
/// "type": "object",
/// "required": [
/// "role",
/// "subject"
/// ],
/// "properties": {
/// "approve": {
/// "description": "Approve-authority: what this subject may **confer on others** by ratifying an approval, as distinct from `scopes`, which is what it may **exercise itself**. The two axes are independent, and that independence is the point — it is what lets a maintainer configure a least-privilege approver who can authorize an operation in a scope it has no authority to perform.\n\nOMISSION MEANS NOTHING IS CONFERRED. An absent `approve`, an absent `all`, and an empty `scopes` are all equivalent to \"this subject may ratify nothing\". A consumer that does not implement this member therefore confers less than the producer intended rather than more, which is the direction a missed member has to fail in.\n\nA subject with approve-authority is NOT thereby authorized to act. Consumers MUST resolve the two axes separately: reading `approve` to answer \"may this party ratify X\" and `scopes` to answer \"may this party do X\". Collapsing them grants an approver the ability to perform what it was only meant to sign off on.",
/// "type": "object",
/// "properties": {
/// "all": {
/// "description": "The subject may confer ANY scope. Takes precedence over `scopes`, which a consumer MUST ignore when this is true. Absent or false → only the scopes listed below, if any.",
/// "default": false,
/// "type": "boolean"
/// },
/// "scopes": {
/// "description": "Opaque scope identifiers this subject may confer, drawn from the same vocabulary as the entry's own `scopes`. Where a maintainer's scopes are hierarchical, conferring a scope confers its descendants — the same containment rule the maintainer already applies to `scopes`, so the two axes cannot disagree about what a scope means. An empty array confers nothing; it is not a wildcard.",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// }
/// },
/// "additionalProperties": false
/// },
/// "createdAt": {
/// "type": "string",
/// "format": "date-time"
/// },
/// "createdBy": {
/// "description": "VID of the party that originally added this entry.",
/// "type": "string"
/// },
/// "expiresAt": {
/// "description": "Optional time after which the entry is no longer effective.",
/// "type": "string",
/// "format": "date-time"
/// },
/// "ext": {
/// "description": "Ecosystem-defined extension members per SPEC.md §4.5.1. Reverse-DNS-namespaced; consumers MUST ignore unrecognized namespaces.",
/// "$ref": "#/definitions/Ext"
/// },
/// "label": {
/// "description": "Optional human-readable label.",
/// "type": "string"
/// },
/// "role": {
/// "description": "Opaque role identifier interpreted by the ACL maintainer.",
/// "type": "string"
/// },
/// "scopes": {
/// "description": "Opaque scope identifiers (e.g. contexts, domains, resource prefixes).",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// },
/// "stepUp": {
/// "description": "Per-entry authentication step-up configuration, consumed by the ACL maintainer when it gates an operation behind a step-up (see auth/step-up/policy/0.1). ADDITIVE-ONLY: a per-entry setting MAY raise the assurance required of this subject above the maintainer's system-wide floor, but MUST NOT lower it. The maintainer resolves the effective requirement as the strictest of (system floor, this entry).",
/// "type": "object",
/// "properties": {
/// "approver": {
/// "description": "VID authorized to ratify step-up for this subject — the `recipient` the maintainer addresses an auth/step-up/approve-request to (e.g. the holder's mobile authenticator or browser companion). Absent → the subject is its own approver (mode `self`) when it holds a usable authenticator; if neither an `approver` nor a self authenticator exists, no step-up method is available for this subject and the maintainer's fail-closed rule applies.",
/// "type": "string"
/// },
/// "require": {
/// "description": "Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).",
/// "type": "string",
/// "enum": [
/// "self",
/// "delegated"
/// ]
/// }
/// },
/// "additionalProperties": false
/// },
/// "subject": {
/// "description": "VID of the party in the ACL. Compared by exact string equality (SPEC.md §4.8); producers SHOULD emit canonical form.",
/// "type": "string"
/// },
/// "updatedAt": {
/// "type": "string",
/// "format": "date-time"
/// },
/// "updatedBy": {
/// "description": "VID of the party that last modified this entry.",
/// "type": "string"
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct AclEntry {
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub approve: ::std::option::Option<AclEntryApprove>,
#[serde(
rename = "createdAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub created_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
///VID of the party that originally added this entry.
#[serde(
rename = "createdBy",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub created_by: ::std::option::Option<::std::string::String>,
///Optional time after which the entry is no longer effective.
#[serde(
rename = "expiresAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub expires_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
///Ecosystem-defined extension members per SPEC.md §4.5.1. Reverse-DNS-namespaced; consumers MUST ignore unrecognized namespaces.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Optional human-readable label.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub label: ::std::option::Option<::std::string::String>,
///Opaque role identifier interpreted by the ACL maintainer.
pub role: ::std::string::String,
///Opaque scope identifiers (e.g. contexts, domains, resource prefixes).
#[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
pub scopes: ::std::vec::Vec<::std::string::String>,
#[serde(
rename = "stepUp",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub step_up: ::std::option::Option<AclEntryStepUp>,
///VID of the party in the ACL. Compared by exact string equality (SPEC.md §4.8); producers SHOULD emit canonical form.
pub subject: ::std::string::String,
#[serde(
rename = "updatedAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub updated_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
///VID of the party that last modified this entry.
#[serde(
rename = "updatedBy",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub updated_by: ::std::option::Option<::std::string::String>,
}
/**Approve-authority: what this subject may **confer on others** by ratifying an approval, as distinct from `scopes`, which is what it may **exercise itself**. The two axes are independent, and that independence is the point — it is what lets a maintainer configure a least-privilege approver who can authorize an operation in a scope it has no authority to perform.
OMISSION MEANS NOTHING IS CONFERRED. An absent `approve`, an absent `all`, and an empty `scopes` are all equivalent to "this subject may ratify nothing". A consumer that does not implement this member therefore confers less than the producer intended rather than more, which is the direction a missed member has to fail in.
A subject with approve-authority is NOT thereby authorized to act. Consumers MUST resolve the two axes separately: reading `approve` to answer "may this party ratify X" and `scopes` to answer "may this party do X". Collapsing them grants an approver the ability to perform what it was only meant to sign off on.*/
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Approve-authority: what this subject may **confer on others** by ratifying an approval, as distinct from `scopes`, which is what it may **exercise itself**. The two axes are independent, and that independence is the point — it is what lets a maintainer configure a least-privilege approver who can authorize an operation in a scope it has no authority to perform.\n\nOMISSION MEANS NOTHING IS CONFERRED. An absent `approve`, an absent `all`, and an empty `scopes` are all equivalent to \"this subject may ratify nothing\". A consumer that does not implement this member therefore confers less than the producer intended rather than more, which is the direction a missed member has to fail in.\n\nA subject with approve-authority is NOT thereby authorized to act. Consumers MUST resolve the two axes separately: reading `approve` to answer \"may this party ratify X\" and `scopes` to answer \"may this party do X\". Collapsing them grants an approver the ability to perform what it was only meant to sign off on.",
/// "type": "object",
/// "properties": {
/// "all": {
/// "description": "The subject may confer ANY scope. Takes precedence over `scopes`, which a consumer MUST ignore when this is true. Absent or false → only the scopes listed below, if any.",
/// "default": false,
/// "type": "boolean"
/// },
/// "scopes": {
/// "description": "Opaque scope identifiers this subject may confer, drawn from the same vocabulary as the entry's own `scopes`. Where a maintainer's scopes are hierarchical, conferring a scope confers its descendants — the same containment rule the maintainer already applies to `scopes`, so the two axes cannot disagree about what a scope means. An empty array confers nothing; it is not a wildcard.",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct AclEntryApprove {
///The subject may confer ANY scope. Takes precedence over `scopes`, which a consumer MUST ignore when this is true. Absent or false → only the scopes listed below, if any.
#[serde(default)]
pub all: bool,
///Opaque scope identifiers this subject may confer, drawn from the same vocabulary as the entry's own `scopes`. Where a maintainer's scopes are hierarchical, conferring a scope confers its descendants — the same containment rule the maintainer already applies to `scopes`, so the two axes cannot disagree about what a scope means. An empty array confers nothing; it is not a wildcard.
#[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
pub scopes: ::std::vec::Vec<::std::string::String>,
}
impl ::std::default::Default for AclEntryApprove {
fn default() -> Self {
Self {
all: Default::default(),
scopes: Default::default(),
}
}
}
///Per-entry authentication step-up configuration, consumed by the ACL maintainer when it gates an operation behind a step-up (see auth/step-up/policy/0.1). ADDITIVE-ONLY: a per-entry setting MAY raise the assurance required of this subject above the maintainer's system-wide floor, but MUST NOT lower it. The maintainer resolves the effective requirement as the strictest of (system floor, this entry).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Per-entry authentication step-up configuration, consumed by the ACL maintainer when it gates an operation behind a step-up (see auth/step-up/policy/0.1). ADDITIVE-ONLY: a per-entry setting MAY raise the assurance required of this subject above the maintainer's system-wide floor, but MUST NOT lower it. The maintainer resolves the effective requirement as the strictest of (system floor, this entry).",
/// "type": "object",
/// "properties": {
/// "approver": {
/// "description": "VID authorized to ratify step-up for this subject — the `recipient` the maintainer addresses an auth/step-up/approve-request to (e.g. the holder's mobile authenticator or browser companion). Absent → the subject is its own approver (mode `self`) when it holds a usable authenticator; if neither an `approver` nor a self authenticator exists, no step-up method is available for this subject and the maintainer's fail-closed rule applies.",
/// "type": "string"
/// },
/// "require": {
/// "description": "Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).",
/// "type": "string",
/// "enum": [
/// "self",
/// "delegated"
/// ]
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct AclEntryStepUp {
///VID authorized to ratify step-up for this subject — the `recipient` the maintainer addresses an auth/step-up/approve-request to (e.g. the holder's mobile authenticator or browser companion). Absent → the subject is its own approver (mode `self`) when it holds a usable authenticator; if neither an `approver` nor a self authenticator exists, no step-up method is available for this subject and the maintainer's fail-closed rule applies.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub approver: ::std::option::Option<::std::string::String>,
///Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub require: ::std::option::Option<AclEntryStepUpRequire>,
}
impl ::std::default::Default for AclEntryStepUp {
fn default() -> Self {
Self {
approver: Default::default(),
require: Default::default(),
}
}
}
///Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).",
/// "type": "string",
/// "enum": [
/// "self",
/// "delegated"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum AclEntryStepUpRequire {
#[serde(rename = "self")]
Self_,
#[serde(rename = "delegated")]
Delegated,
}
impl ::std::fmt::Display for AclEntryStepUpRequire {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Self_ => f.write_str("self"),
Self::Delegated => f.write_str("delegated"),
}
}
}
impl ::std::str::FromStr for AclEntryStepUpRequire {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"self" => Ok(Self::Self_),
"delegated" => Ok(Self::Delegated),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for AclEntryStepUpRequire {
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 AclEntryStepUpRequire {
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 AclEntryStepUpRequire {
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())
})
}
}
///Amend the non-role attributes of an existing ACL entry: its label, scopes, expiry, step-up requirement, or approve-authority. Role changes are NOT expressible here — they go through acl/change-role, which requires the current role as a compare-and-swap.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "$id": "https://trusttasks.org/spec/acl/update/0.1",
/// "title": "Payload",
/// "description": "Amend the non-role attributes of an existing ACL entry: its label, scopes, expiry, step-up requirement, or approve-authority. Role changes are NOT expressible here — they go through acl/change-role, which requires the current role as a compare-and-swap.",
/// "type": "object",
/// "required": [
/// "subject"
/// ],
/// "properties": {
/// "approve": {
/// "description": "Replacement approve-authority — what the subject may CONFER on others, as distinct from what it may exercise. Granting or widening this is an escalation vector (a subject able to confer can manufacture an approver for an operation it could not authorize), so a consumer SHOULD gate it more strictly than the other members here.",
/// "type": "object",
/// "properties": {
/// "all": {
/// "description": "The subject may confer any scope.",
/// "type": "boolean"
/// },
/// "scopes": {
/// "description": "Scopes the subject may confer. Empty confers nothing; it is not a wildcard.",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// }
/// },
/// "additionalProperties": false
/// },
/// "expiresAt": {
/// "description": "Replacement expiry. Explicit `null` makes the entry permanent — a privilege increase, which a consumer SHOULD gate at least as strictly as the original grant.",
/// "type": [
/// "string",
/// "null"
/// ],
/// "format": "date-time"
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// },
/// "label": {
/// "description": "Replacement human-readable label. Explicit `null` clears it. Omitted leaves it unchanged.",
/// "type": [
/// "string",
/// "null"
/// ]
/// },
/// "reason": {
/// "description": "Optional human-readable rationale, recorded with the change.",
/// "type": "string"
/// },
/// "scopes": {
/// "description": "Replacement scope set, applied wholesale rather than merged — a caller that means to add one sends the full intended set. NARROWING THE SET IS A REVOCATION and a consumer MUST refuse it here, directing the caller to acl/revoke, so that every removal of authority passes through the task that is audited and reasoned as a revocation.",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// },
/// "stepUp": {
/// "description": "Replacement per-entry step-up configuration. Same additive-only rule as on the entry itself: it MAY raise the assurance required of this subject above the system floor but MUST NOT lower it.",
/// "type": "object",
/// "properties": {
/// "approver": {
/// "description": "VID that ratifies step-up for this subject. `null` clears it.",
/// "type": [
/// "string",
/// "null"
/// ]
/// },
/// "require": {
/// "description": "Minimum step-up mode. `null` clears the per-entry override.",
/// "type": [
/// "string",
/// "null"
/// ],
/// "enum": [
/// "self",
/// "delegated",
/// null
/// ]
/// }
/// },
/// "additionalProperties": false
/// },
/// "subject": {
/// "description": "VID of the entry to amend. The entry MUST already exist; this task does not create one (use acl/grant).",
/// "type": "string",
/// "minLength": 1
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Payload {
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub approve: ::std::option::Option<PayloadApprove>,
///Replacement expiry. Explicit `null` makes the entry permanent — a privilege increase, which a consumer SHOULD gate at least as strictly as the original grant.
#[serde(
rename = "expiresAt",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub expires_at: ::std::option::Option<::chrono::DateTime<::chrono::offset::Utc>>,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
///Replacement human-readable label. Explicit `null` clears it. Omitted leaves it unchanged.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub label: ::std::option::Option<::std::string::String>,
///Optional human-readable rationale, recorded with the change.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub reason: ::std::option::Option<::std::string::String>,
///Replacement scope set, applied wholesale rather than merged — a caller that means to add one sends the full intended set. NARROWING THE SET IS A REVOCATION and a consumer MUST refuse it here, directing the caller to acl/revoke, so that every removal of authority passes through the task that is audited and reasoned as a revocation.
#[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
pub scopes: ::std::vec::Vec<::std::string::String>,
#[serde(
rename = "stepUp",
default,
skip_serializing_if = "::std::option::Option::is_none"
)]
pub step_up: ::std::option::Option<PayloadStepUp>,
///VID of the entry to amend. The entry MUST already exist; this task does not create one (use acl/grant).
pub subject: PayloadSubject,
}
///Replacement approve-authority — what the subject may CONFER on others, as distinct from what it may exercise. Granting or widening this is an escalation vector (a subject able to confer can manufacture an approver for an operation it could not authorize), so a consumer SHOULD gate it more strictly than the other members here.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Replacement approve-authority — what the subject may CONFER on others, as distinct from what it may exercise. Granting or widening this is an escalation vector (a subject able to confer can manufacture an approver for an operation it could not authorize), so a consumer SHOULD gate it more strictly than the other members here.",
/// "type": "object",
/// "properties": {
/// "all": {
/// "description": "The subject may confer any scope.",
/// "type": "boolean"
/// },
/// "scopes": {
/// "description": "Scopes the subject may confer. Empty confers nothing; it is not a wildcard.",
/// "type": "array",
/// "items": {
/// "type": "string"
/// }
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct PayloadApprove {
///The subject may confer any scope.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub all: ::std::option::Option<bool>,
///Scopes the subject may confer. Empty confers nothing; it is not a wildcard.
#[serde(default, skip_serializing_if = "::std::vec::Vec::is_empty")]
pub scopes: ::std::vec::Vec<::std::string::String>,
}
impl ::std::default::Default for PayloadApprove {
fn default() -> Self {
Self {
all: Default::default(),
scopes: Default::default(),
}
}
}
///Replacement per-entry step-up configuration. Same additive-only rule as on the entry itself: it MAY raise the assurance required of this subject above the system floor but MUST NOT lower it.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Replacement per-entry step-up configuration. Same additive-only rule as on the entry itself: it MAY raise the assurance required of this subject above the system floor but MUST NOT lower it.",
/// "type": "object",
/// "properties": {
/// "approver": {
/// "description": "VID that ratifies step-up for this subject. `null` clears it.",
/// "type": [
/// "string",
/// "null"
/// ]
/// },
/// "require": {
/// "description": "Minimum step-up mode. `null` clears the per-entry override.",
/// "type": [
/// "string",
/// "null"
/// ],
/// "enum": [
/// "self",
/// "delegated",
/// null
/// ]
/// }
/// },
/// "additionalProperties": false
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct PayloadStepUp {
///VID that ratifies step-up for this subject. `null` clears it.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub approver: ::std::option::Option<::std::string::String>,
///Minimum step-up mode. `null` clears the per-entry override.
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub require: ::std::option::Option<PayloadStepUpRequire>,
}
impl ::std::default::Default for PayloadStepUp {
fn default() -> Self {
Self {
approver: Default::default(),
require: Default::default(),
}
}
}
///Minimum step-up mode. `null` clears the per-entry override.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "Minimum step-up mode. `null` clears the per-entry override.",
/// "type": "string",
/// "enum": [
/// "self",
/// "delegated"
/// ]
///}
/// ```
/// </details>
#[derive(
::serde::Deserialize,
::serde::Serialize,
Clone,
Copy,
Debug,
Eq,
Hash,
Ord,
PartialEq,
PartialOrd,
)]
pub enum PayloadStepUpRequire {
#[serde(rename = "self")]
Self_,
#[serde(rename = "delegated")]
Delegated,
}
impl ::std::fmt::Display for PayloadStepUpRequire {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match *self {
Self::Self_ => f.write_str("self"),
Self::Delegated => f.write_str("delegated"),
}
}
}
impl ::std::str::FromStr for PayloadStepUpRequire {
type Err = self::error::ConversionError;
fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
match value {
"self" => Ok(Self::Self_),
"delegated" => Ok(Self::Delegated),
_ => Err("invalid value".into()),
}
}
}
impl ::std::convert::TryFrom<&str> for PayloadStepUpRequire {
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 PayloadStepUpRequire {
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 PayloadStepUpRequire {
type Error = self::error::ConversionError;
fn try_from(
value: ::std::string::String,
) -> ::std::result::Result<Self, self::error::ConversionError> {
value.parse()
}
}
///VID of the entry to amend. The entry MUST already exist; this task does not create one (use acl/grant).
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "description": "VID of the entry to amend. The entry MUST already exist; this task does not create one (use acl/grant).",
/// "type": "string",
/// "minLength": 1
///}
/// ```
/// </details>
#[derive(::serde::Serialize, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[serde(transparent)]
pub struct PayloadSubject(::std::string::String);
impl ::std::ops::Deref for PayloadSubject {
type Target = ::std::string::String;
fn deref(&self) -> &::std::string::String {
&self.0
}
}
impl ::std::convert::From<PayloadSubject> for ::std::string::String {
fn from(value: PayloadSubject) -> Self {
value.0
}
}
impl ::std::str::FromStr for PayloadSubject {
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 PayloadSubject {
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 PayloadSubject {
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 PayloadSubject {
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 PayloadSubject {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
where
D: ::serde::Deserializer<'de>,
{
::std::string::String::deserialize(deserializer)?
.parse()
.map_err(|e: self::error::ConversionError| {
<D::Error as ::serde::de::Error>::custom(e.to_string())
})
}
}
///`Response`
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
/// "title": "Response",
/// "type": "object",
/// "required": [
/// "entry"
/// ],
/// "properties": {
/// "entry": {
/// "description": "The realized entry the maintainer now holds, after the amendment.",
/// "$ref": "#/definitions/AclEntry"
/// },
/// "ext": {
/// "$ref": "#/definitions/Ext"
/// }
/// },
/// "additionalProperties": false,
/// "$anchor": "response"
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug)]
#[serde(deny_unknown_fields)]
pub struct Response {
///The realized entry the maintainer now holds, after the amendment.
pub entry: AclEntry,
#[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
pub ext: ::std::option::Option<Ext>,
}
impl crate::Payload for Payload {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/acl/update/0.1";
const IS_PROOF_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
}
impl crate::Payload for Response {
const TYPE_URI: &'static str = "https://trusttasks.org/spec/acl/update/0.1#response";
const IS_PROOF_REQUIRED: bool = true;
const IS_RECIPIENT_REQUIRED: bool = true;
}
#[cfg(feature = "validate")]
impl crate::validate::ValidatedPayload for Payload {
const SCHEMA_JSON: &'static str = "{\n \"$defs\": {\n \"AclEntry\": {\n \"additionalProperties\": false,\n \"properties\": {\n \"approve\": {\n \"additionalProperties\": false,\n \"description\": \"Approve-authority: what this subject may **confer on others** by ratifying an approval, as distinct from `scopes`, which is what it may **exercise itself**. The two axes are independent, and that independence is the point — it is what lets a maintainer configure a least-privilege approver who can authorize an operation in a scope it has no authority to perform.\\n\\nOMISSION MEANS NOTHING IS CONFERRED. An absent `approve`, an absent `all`, and an empty `scopes` are all equivalent to \\\"this subject may ratify nothing\\\". A consumer that does not implement this member therefore confers less than the producer intended rather than more, which is the direction a missed member has to fail in.\\n\\nA subject with approve-authority is NOT thereby authorized to act. Consumers MUST resolve the two axes separately: reading `approve` to answer \\\"may this party ratify X\\\" and `scopes` to answer \\\"may this party do X\\\". Collapsing them grants an approver the ability to perform what it was only meant to sign off on.\",\n \"properties\": {\n \"all\": {\n \"default\": false,\n \"description\": \"The subject may confer ANY scope. Takes precedence over `scopes`, which a consumer MUST ignore when this is true. Absent or false → only the scopes listed below, if any.\",\n \"type\": \"boolean\"\n },\n \"scopes\": {\n \"description\": \"Opaque scope identifiers this subject may confer, drawn from the same vocabulary as the entry's own `scopes`. Where a maintainer's scopes are hierarchical, conferring a scope confers its descendants — the same containment rule the maintainer already applies to `scopes`, so the two axes cannot disagree about what a scope means. An empty array confers nothing; it is not a wildcard.\",\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\"\n }\n },\n \"type\": \"object\"\n },\n \"createdAt\": {\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"createdBy\": {\n \"description\": \"VID of the party that originally added this entry.\",\n \"type\": \"string\"\n },\n \"expiresAt\": {\n \"description\": \"Optional time after which the entry is no longer effective.\",\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\",\n \"description\": \"Ecosystem-defined extension members per SPEC.md §4.5.1. Reverse-DNS-namespaced; consumers MUST ignore unrecognized namespaces.\"\n },\n \"label\": {\n \"description\": \"Optional human-readable label.\",\n \"type\": \"string\"\n },\n \"role\": {\n \"description\": \"Opaque role identifier interpreted by the ACL maintainer.\",\n \"type\": \"string\"\n },\n \"scopes\": {\n \"description\": \"Opaque scope identifiers (e.g. contexts, domains, resource prefixes).\",\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\"\n },\n \"stepUp\": {\n \"additionalProperties\": false,\n \"description\": \"Per-entry authentication step-up configuration, consumed by the ACL maintainer when it gates an operation behind a step-up (see auth/step-up/policy/0.1). ADDITIVE-ONLY: a per-entry setting MAY raise the assurance required of this subject above the maintainer's system-wide floor, but MUST NOT lower it. The maintainer resolves the effective requirement as the strictest of (system floor, this entry).\",\n \"properties\": {\n \"approver\": {\n \"description\": \"VID authorized to ratify step-up for this subject — the `recipient` the maintainer addresses an auth/step-up/approve-request to (e.g. the holder's mobile authenticator or browser companion). Absent → the subject is its own approver (mode `self`) when it holds a usable authenticator; if neither an `approver` nor a self authenticator exists, no step-up method is available for this subject and the maintainer's fail-closed rule applies.\",\n \"type\": \"string\"\n },\n \"require\": {\n \"description\": \"Minimum step-up mode this subject MUST satisfy for gated operations, raising the system floor. `self` = the subject re-authenticates its own session; `delegated` = a separate `approver` MUST ratify. Omitted → the system floor applies unchanged. A value weaker than the resolved floor is ignored (additive-only).\",\n \"enum\": [\n \"self\",\n \"delegated\"\n ],\n \"type\": \"string\"\n }\n },\n \"type\": \"object\"\n },\n \"subject\": {\n \"description\": \"VID of the party in the ACL. Compared by exact string equality (SPEC.md §4.8); producers SHOULD emit canonical form.\",\n \"type\": \"string\"\n },\n \"updatedAt\": {\n \"format\": \"date-time\",\n \"type\": \"string\"\n },\n \"updatedBy\": {\n \"description\": \"VID of the party that last modified this entry.\",\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"subject\",\n \"role\"\n ],\n \"title\": \"AclEntry\",\n \"type\": \"object\"\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 \"Response\": {\n \"$anchor\": \"response\",\n \"additionalProperties\": false,\n \"properties\": {\n \"entry\": {\n \"$ref\": \"#/$defs/AclEntry\",\n \"description\": \"The realized entry the maintainer now holds, after the amendment.\"\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n }\n },\n \"required\": [\n \"entry\"\n ],\n \"title\": \"ACL Update — response payload\",\n \"type\": \"object\"\n }\n },\n \"$id\": \"https://trusttasks.org/spec/acl/update/0.1\",\n \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n \"additionalProperties\": false,\n \"description\": \"Amend the non-role attributes of an existing ACL entry: its label, scopes, expiry, step-up requirement, or approve-authority. Role changes are NOT expressible here — they go through acl/change-role, which requires the current role as a compare-and-swap.\",\n \"properties\": {\n \"approve\": {\n \"additionalProperties\": false,\n \"description\": \"Replacement approve-authority — what the subject may CONFER on others, as distinct from what it may exercise. Granting or widening this is an escalation vector (a subject able to confer can manufacture an approver for an operation it could not authorize), so a consumer SHOULD gate it more strictly than the other members here.\",\n \"properties\": {\n \"all\": {\n \"description\": \"The subject may confer any scope.\",\n \"type\": \"boolean\"\n },\n \"scopes\": {\n \"description\": \"Scopes the subject may confer. Empty confers nothing; it is not a wildcard.\",\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\"\n }\n },\n \"type\": \"object\"\n },\n \"expiresAt\": {\n \"description\": \"Replacement expiry. Explicit `null` makes the entry permanent — a privilege increase, which a consumer SHOULD gate at least as strictly as the original grant.\",\n \"format\": \"date-time\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"ext\": {\n \"$ref\": \"#/$defs/Ext\"\n },\n \"label\": {\n \"description\": \"Replacement human-readable label. Explicit `null` clears it. Omitted leaves it unchanged.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"reason\": {\n \"description\": \"Optional human-readable rationale, recorded with the change.\",\n \"type\": \"string\"\n },\n \"scopes\": {\n \"description\": \"Replacement scope set, applied wholesale rather than merged — a caller that means to add one sends the full intended set. NARROWING THE SET IS A REVOCATION and a consumer MUST refuse it here, directing the caller to acl/revoke, so that every removal of authority passes through the task that is audited and reasoned as a revocation.\",\n \"items\": {\n \"type\": \"string\"\n },\n \"type\": \"array\"\n },\n \"stepUp\": {\n \"additionalProperties\": false,\n \"description\": \"Replacement per-entry step-up configuration. Same additive-only rule as on the entry itself: it MAY raise the assurance required of this subject above the system floor but MUST NOT lower it.\",\n \"properties\": {\n \"approver\": {\n \"description\": \"VID that ratifies step-up for this subject. `null` clears it.\",\n \"type\": [\n \"string\",\n \"null\"\n ]\n },\n \"require\": {\n \"description\": \"Minimum step-up mode. `null` clears the per-entry override.\",\n \"enum\": [\n \"self\",\n \"delegated\",\n null\n ],\n \"type\": [\n \"string\",\n \"null\"\n ]\n }\n },\n \"type\": \"object\"\n },\n \"subject\": {\n \"description\": \"VID of the entry to amend. The entry MUST already exist; this task does not create one (use acl/grant).\",\n \"minLength\": 1,\n \"type\": \"string\"\n }\n },\n \"required\": [\n \"subject\"\n ],\n \"title\": \"ACL Update — payload\",\n \"type\": \"object\"\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).
/// Each fixture in `payload.invalid-examples.json` MUST be
/// rejected by at least one of: serde deserialization, or
/// JSON-Schema validation under the `validate` feature. The
/// fixture file documents the producer-side bug class that
/// each payload exemplifies; this generated test pins it.
#[cfg(feature = "validate")]
#[test]
fn rejects_invalid_examples() {
use crate::validate::ValidatedPayload;
let fixtures: &[(&str, &str)] = &[
("`subject` is required.", "{\n \"label\": \"ops runner\"\n}"),
("`subject` must be non-empty.", "{\n \"subject\": \"\"\n}"),
(
"Role is not amendable here — acl/change-role owns that transition, with a compare-and-swap.",
"{\n \"role\": \"admin\",\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"Unknown top-level member is rejected.",
"{\n \"__x__\": true,\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"`scopes` must be an array of strings.",
"{\n \"scopes\": \"tenant-a\",\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"`stepUp.require` is a closed enum.",
"{\n \"stepUp\": {\n \"require\": \"sometimes\"\n },\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"`approve.all` must be a boolean.",
"{\n \"approve\": {\n \"all\": \"yes\"\n },\n \"subject\": \"did:web:alice.example\"\n}",
),
(
"`expiresAt` must be an RFC 3339 date-time.",
"{\n \"expiresAt\": \"next tuesday\",\n \"subject\": \"did:web:alice.example\"\n}",
),
];
for (i, (note, raw)) in fixtures.iter().enumerate() {
let value: serde_json::Value = match serde_json::from_str(raw) {
Ok(v) => v,
Err(_) => continue,
};
let serde_ok = serde_json::from_value::<super::Payload>(value.clone()).is_ok();
let schema_ok = super::Payload::validate_value(&value).is_ok();
assert!(
!(serde_ok && schema_ok),
"invalid-example #{} ({:?}) was accepted by both serde and JSON Schema; \
the fixture's stated failure class is no longer caught:\n{}",
i + 1,
note,
raw
);
}
}
}