From 5570a3f00cd13f9a2a27832d58f1c2880f327685 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Thu, 22 Oct 2026 06:16:15 +0800 Subject: [PATCH 01/13] feat(crypto): add native interoperable encrypted envelopes and local CLI --- .github/workflows/rust-crypto.yml | 49 ++++ .github/workflows/rust-wire.yml | 6 +- rust/Cargo.toml | 6 + rust/MIGRATION.md | 16 +- rust/README.md | 39 ++- rust/crates/msg-crypto/Cargo.toml | 10 + rust/crates/msg-crypto/src/lib.rs | 4 +- rust/crates/msg-crypto/src/main.rs | 144 +++++++++++ rust/crates/msg-crypto/src/sealed_box.rs | 316 +++++++++++++++++++++++ rust/tests/test_crypto_parity.py | 243 +++++++++++++++++ 10 files changed, 823 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/rust-crypto.yml create mode 100644 rust/crates/msg-crypto/src/main.rs create mode 100644 rust/crates/msg-crypto/src/sealed_box.rs create mode 100644 rust/tests/test_crypto_parity.py diff --git a/.github/workflows/rust-crypto.yml b/.github/workflows/rust-crypto.yml new file mode 100644 index 00000000..ffaa326c --- /dev/null +++ b/.github/workflows/rust-crypto.yml @@ -0,0 +1,49 @@ +name: Rust encryption +on: + push: + paths: ['rust/Cargo.*', 'rust/crates/msg-core/**', 'rust/crates/msg-crypto/**', 'rust/tests/test_crypto_parity.py', '.github/workflows/rust-crypto.yml'] + pull_request: + paths: ['rust/Cargo.*', 'rust/crates/msg-core/**', 'rust/crates/msg-crypto/**', 'rust/tests/test_crypto_parity.py', '.github/workflows/rust-crypto.yml'] + workflow_dispatch: +permissions: + contents: read +jobs: + crypto: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: '3.15' + allow-prereleases: true + - name: Install test dependencies + run: | + rustup toolchain install 1.85.0 --profile minimal --component rustfmt,clippy + python -m pip install cryptography==46.0.3 ruff==0.16.9 + - name: Format and reference harness style + run: | + cargo +1.85.0 fmt --manifest-path rust/Cargo.toml --all -- --check + python -m ruff check rust/tests/test_crypto_parity.py + python -m ruff format --check rust/tests/test_crypto_parity.py + - name: Native tests and lint + if: ${{ !cancelled() }} + working-directory: rust + run: | + cargo +1.85.0 test -p msg-crypto + cargo +1.85.0 clippy -p msg-crypto --all-targets -- -D warnings + cargo +1.85.0 build -p msg-crypto + - name: Python and Rust interoperability + env: + PYTHONPATH: src + run: python rust/tests/test_crypto_parity.py --binary rust/target/debug/msg-crypto --report rust/artifacts/crypto.json + - uses: actions/upload-artifact@v7 + if: ${{ always() }} + with: + name: crypto-${{ github.sha }} + path: | + rust/Cargo.lock + rust/artifacts/crypto.json + if-no-files-found: error diff --git a/.github/workflows/rust-wire.yml b/.github/workflows/rust-wire.yml index d282c278..07786e2a 100644 --- a/.github/workflows/rust-wire.yml +++ b/.github/workflows/rust-wire.yml @@ -67,7 +67,8 @@ jobs: env: PYTHONPATH: src run: | - cargo +1.85.0 build --locked --manifest-path rust/Cargo.toml --workspace --examples + cargo +1.85.0 build --locked --manifest-path rust/Cargo.toml --workspace --examples --bins + python rust/tests/test_crypto_parity.py --binary rust/target/debug/msg-crypto --report rust/artifacts/crypto-debug.json python rust/tests/test_python_parity.py --binary rust/target/debug/examples/wire_check --report rust/artifacts/parity.json python rust/tests/test_identity_parity.py --binary rust/target/debug/examples/authority_check --report rust/artifacts/identity-parity.json python rust/tests/test_storage_parity.py --binary rust/target/debug/examples/writer_check --report rust/artifacts/storage-parity.json @@ -81,7 +82,8 @@ jobs: env: PYTHONPATH: src run: | - cargo +1.85.0 build --release --locked --manifest-path rust/Cargo.toml --workspace --examples + cargo +1.85.0 build --release --locked --manifest-path rust/Cargo.toml --workspace --examples --bins + python rust/tests/test_crypto_parity.py --binary rust/target/release/msg-crypto --report rust/artifacts/crypto-release.json python rust/tests/test_python_parity.py --binary rust/target/release/examples/wire_check --report rust/artifacts/parity-release.json python rust/tests/test_identity_parity.py --binary rust/target/release/examples/authority_check --report rust/artifacts/identity-parity-release.json python rust/tests/test_storage_parity.py --binary rust/target/release/examples/writer_check --report rust/artifacts/storage-parity-release.json diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e9b953c1..260dc4e1 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -10,14 +10,20 @@ license = "MIT" publish = false [workspace.dependencies] +aes-gcm = "=0.10.3" base64 = "=0.22.1" chrono = { version = "=0.4.42", default-features = false, features = ["std"] } ed25519-dalek = "=2.2.0" +hkdf = "=0.12.4" +rand_core = { version = "=0.6.4", features = ["getrandom"] } +rustix = { version = "=1.1.5", features = ["process"] } ryu = "=1.0.20" serde = { version = "=1.0.228", features = ["derive"] } serde_json = { version = "=1.0.145", features = ["raw_value", "arbitrary_precision"] } sha2 = "=0.10.9" subtle = "=2.6.1" +x25519-dalek = { version = "=2.0.1", features = ["static_secrets"] } +zeroize = "=1.9.0" fs2 = "=0.4.3" libc = "=0.2.190" rusqlite = { version = "=0.32.1", features = ["bundled"] } diff --git a/rust/MIGRATION.md b/rust/MIGRATION.md index 64f557f6..60aad44c 100644 --- a/rust/MIGRATION.md +++ b/rust/MIGRATION.md @@ -14,15 +14,27 @@ Keep the IDs stable in later progress reports; split/redefine one only explicitl | M05 | Native Git/blob content, ranges, pins and revision graphs; valid-data interoperability | Implemented in slice 5 | | M06 | LFS shared-object lifecycle and durable metadata/content recovery; no ambiguous-commit data loss | Remaining | | M07 | Real operation/capability registry, authorized executor, business handlers, receipts and secret delivery | Remaining | -| M08 | Native identity/OAuth/browser-session issuance, rotation, refresh, revocation and encrypted-key wrapping | Remaining | +| M08 | Native key identity/browser-session issuance, rotation, refresh, revocation and encrypted-key wrapping | Remaining | | M09 | Native `msgd serve`, HTTP/MCP/GET and other supported transports with current contracts | Remaining | | M10 | Native `msgd worker`, leases/outbox, retries, recovery fences and actual side effects | Remaining | | M11 | Installable native daemon, isolated cutover/rollback rehearsal, full end-to-end acceptance and equivalent benchmarks | Remaining | -Python retains `msg` CLI, administrator/migration scripts and developer tooling. +Python remains the production `msg` CLI, administrator/migration scripts and +developer tooling while individual client features move to Rust. There is still no native production daemon in this PR. Root signing remains local only. Do not merge/deploy solely because these primitive suites pass. +## Local encryption slice + +The existing `msg-crypto` crate now includes native `msg-x25519-v1` encryption, +decryption, bounded envelope validation and a Unix `keygen/encrypt/decrypt` CLI. +It keeps Python's wire format and safe local-file behavior without a network or +new account framework. Python remains the compatibility reference. + +This extends the cryptographic foundation; it does not complete identity +issuance, password-wrapped signing keys, remote keystore operations or a native +server. The fixed completed/remaining count therefore stays **5 / 6**. + ## Slice 5 acceptance and boundaries - 14 new native content tests; current workspace: 46 unit tests and 5 doc tests. diff --git a/rust/README.md b/rust/README.md index 444645d8..3bd9080e 100644 --- a/rust/README.md +++ b/rust/README.md @@ -1,10 +1,36 @@ # Native Rust server migration -Python keeps the CLI, administrator/migration scripts, and development helpers. +Python remains the production entry point while native features are migrated. Native Rust code is not yet a replacement for the running Python service. Do not change production entry points until the remaining executor, transport, content-recovery, worker, and deployment gates below pass. +## Local encryption, in Rust + +`msg-crypto` is a small Unix CLI in the existing crypto crate. It generates an +independent X25519 key and encrypts/decrypts the same `msg-x25519-v1` files as Python: + +```sh +cargo build --locked --release --manifest-path rust/Cargo.toml -p msg-crypto +rust/target/release/msg-crypto keygen private.key +# Use the recipient public key from the JSON output above. +rust/target/release/msg-crypto encrypt RECIPIENT_BASE64 message.txt message.sealed +rust/target/release/msg-crypto decrypt private.key message.sealed restored.txt +``` + +No network, registration or third-party identity is involved. New files use mode +0600 and exclusive creation; existing paths are never overwritten. Private-key +reads check the opened file's owner, permissions and type, rejecting symlinks. +Choose a trusted local directory. Encryption accepts up to 512 KiB of plaintext; +decryption accepts existing envelopes up to 1 MiB, including older larger messages. +Owned secret buffers clear on drop. Encryption protects the recipient's content; +sender authentication remains the separate Ed25519 signing protocol. + +The library lives in `msg_crypto::sealed_box` and reuses X25519, HKDF-SHA256 and +AES-256-GCM. `validate_envelope` checks shape only. The differential test imports +the actual Python implementation and exercises both directions plus file and +tampering boundaries. This is a usable encryption slice, not a native daemon. + ## Fifth implementation slice: native Git/blob content storage `msg-storage::content::GitContentStore` ports the existing `private.git`, `index`, @@ -102,8 +128,9 @@ one-time offline editor export workflows are removed. ## Ownership boundary -Retain Python `msg` CLI, administration/migration scripts and developer tools. -Migrate server-side `msgd serve`, `msgd worker`, domain/identity, storage, +Migrate client and runtime features incrementally, keeping Python as the +compatibility reference and for administration/migration and developer tools. +The server scope remains `msgd serve`, `msgd worker`, domain/identity, storage, HTTP/MCP and crypto/protocol to Rust. Do not rename endpoints or invent a second signature protocol. The tests import the actual Python implementation in the same checkout, rather than a translated copy of the expected behavior. @@ -117,7 +144,7 @@ The original inspected main baseline is | Crate | Implemented responsibility | Python reference | |---|---|---| | `msg-core` | Bounded strict JSON, duplicate rejection, canonical numbers/Unicode, lossless typed JSON, SHA-256/base64url | `core/codec.py` | -| `msg-crypto` | Existing key/subject IDs, purpose-framed Ed25519 signing/verification | `security/crypto.py` | +| `msg-crypto` | Existing key/subject IDs, purpose-framed Ed25519 signing/verification, X25519 encrypted envelopes and local encryption CLI | `security/crypto.py`, `security/sealed_box.py`, `client_secrets.py` | | `msg-protocol` | Request shape/defaults, UTC time, business digest/signature bytes, immutable field access | `core/packet.py`, `core/requests.py`, `core/models.py` | | `msg-identity` | Typed identity/credential/certificate/CSR records, credential and current-state admission, pinned CA chains, live delegation, scope/ceiling/mode primitives, key-approved browser/API binding and token-recovery validation, transaction-bound resource authorization | `security/authentication.py`, `certificates.py`, `policy.py`, `sessions.py` | | `msg-storage` | Transaction-bound authority reads; SQLite/PostgreSQL writers and typed records, savepoints/rollback effects; native Git/blob content, ranges, pins and revision trees | `storage/sqlite.py`, `storage/postgres.py`, `storage/git.py` and their record/session modules | @@ -216,7 +243,9 @@ python -m ruff format --check rust/tests cargo +1.85.0 test --locked --manifest-path rust/Cargo.toml --workspace --all-targets cargo +1.85.0 test --locked --manifest-path rust/Cargo.toml --workspace --doc cargo +1.85.0 clippy --locked --manifest-path rust/Cargo.toml --workspace --all-targets -- -D warnings -cargo +1.85.0 build --locked --manifest-path rust/Cargo.toml --workspace --examples +cargo +1.85.0 build --locked --manifest-path rust/Cargo.toml --workspace --examples --bins +PYTHONPATH=src python rust/tests/test_crypto_parity.py \ + --binary rust/target/debug/msg-crypto --report rust/artifacts/crypto-debug.json PYTHONPATH=src python rust/tests/test_python_parity.py \ --binary rust/target/debug/examples/wire_check --report rust/artifacts/parity.json PYTHONPATH=src python rust/tests/test_identity_parity.py \ diff --git a/rust/crates/msg-crypto/Cargo.toml b/rust/crates/msg-crypto/Cargo.toml index a013d7dd..f215cde0 100644 --- a/rust/crates/msg-crypto/Cargo.toml +++ b/rust/crates/msg-crypto/Cargo.toml @@ -7,10 +7,20 @@ license.workspace = true publish.workspace = true [dependencies] +aes-gcm.workspace = true ed25519-dalek.workspace = true +hkdf.workspace = true +rand_core.workspace = true serde.workspace = true +serde_json.workspace = true sha2.workspace = true +x25519-dalek.workspace = true +zeroize.workspace = true msg-core = { path = "../msg-core" } +[target.'cfg(unix)'.dependencies] +libc.workspace = true +rustix.workspace = true + [lints] workspace = true diff --git a/rust/crates/msg-crypto/src/lib.rs b/rust/crates/msg-crypto/src/lib.rs index ee4b82a8..7e07f623 100644 --- a/rust/crates/msg-crypto/src/lib.rs +++ b/rust/crates/msg-crypto/src/lib.rs @@ -1,4 +1,6 @@ -//! Native signature primitives, not authentication or certificate authorization. +//! Native signing and recipient encryption, not authentication or authorization. +pub mod sealed_box; + use ed25519_dalek::{Signer as _, SigningKey, VerifyingKey}; use msg_core::{b64, unb64, Error, Result}; use serde::{Deserialize, Serialize}; diff --git a/rust/crates/msg-crypto/src/main.rs b/rust/crates/msg-crypto/src/main.rs new file mode 100644 index 00000000..70cb3f3b --- /dev/null +++ b/rust/crates/msg-crypto/src/main.rs @@ -0,0 +1,144 @@ +//! Small, offline encryption CLI. No account, network or server dependency. +use msg_core::{Error, Result}; +use std::io::Write; + +const HELP: &str = "msg-crypto — local MSG encryption\n\ +Usage:\n\ + msg-crypto keygen PRIVATE_KEY_FILE\n\ + msg-crypto encrypt RECIPIENT_BASE64 INPUT OUTPUT\n\ + msg-crypto decrypt PRIVATE_KEY_FILE INPUT OUTPUT\n\n\ +Outputs are new private files; existing paths are never overwritten.\n\ +Keep the private key locally. Share only the recipient public key.\n\ +Encryption does not authenticate the sender. Unix only."; + +fn main() -> std::process::ExitCode { + match run() { + Ok(()) => std::process::ExitCode::SUCCESS, + Err(error) => { + let _ = writeln!(std::io::stderr().lock(), "msg-crypto: {error}"); + std::process::ExitCode::FAILURE + } + } +} + +fn run() -> Result<()> { + let args: Vec<_> = std::env::args_os().skip(1).collect(); + if args.is_empty() || matches!(args.as_slice(), [arg] if arg == "--help" || arg == "-h") { + return print_output(HELP); + } + #[cfg(unix)] + { + local::run(&args) + } + #[cfg(not(unix))] + { + Err(Error("unix_required_for_private_files")) + } +} + +fn print_output(text: &str) -> Result<()> { + writeln!(std::io::stdout().lock(), "{text}").map_err(|_| Error("stdout_write_failed")) +} + +#[cfg(unix)] +mod local { + use super::*; + use msg_core::{b64, unb64}; + use msg_crypto::sealed_box::{ + decrypt, encrypt, generate_key, FORMAT, MAX_ENVELOPE_BYTES, MAX_PLAINTEXT_BYTES, + }; + use std::{ + ffi::OsString, + fs::{self, DirBuilder, OpenOptions}, + io::{Read, Write}, + os::unix::fs::{DirBuilderExt, MetadataExt, OpenOptionsExt}, + path::Path, + }; + use zeroize::Zeroizing; + + pub fn run(args: &[OsString]) -> Result<()> { + match args { + [command, output] if command == "keygen" => { + let (private, public) = generate_key()?; + let result = serde_json::to_string(&serde_json::json!({ + "format": FORMAT, + "recipient": b64(&public), + "private_key_file": output.to_str().ok_or(Error("invalid_output_path"))?, + })) + .map_err(|_| Error("invalid_output_path"))?; + write_new(Path::new(output), &private[..])?; + print_output(&result) + } + [command, recipient, input, output] if command == "encrypt" => { + let recipient = unb64( + recipient + .to_str() + .ok_or(Error("invalid_encryption_recipient"))?, + 32, + )?; + let plaintext = read(Path::new(input), MAX_PLAINTEXT_BYTES, false)?; + let envelope = encrypt(&plaintext, &recipient)?; + write_new(Path::new(output), &envelope) + } + [command, private, input, output] if command == "decrypt" => { + let private = read(Path::new(private), 32, true)?; + let envelope = read(Path::new(input), MAX_ENVELOPE_BYTES, false)?; + let plaintext = decrypt(&envelope, &private)?; + write_new(Path::new(output), &plaintext) + } + _ => Err(Error("invalid_arguments; use --help")), + } + } + + fn read(path: &Path, limit: usize, private: bool) -> Result>> { + let file = OpenOptions::new() + .read(true) + .custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK | libc::O_CLOEXEC) + .open(path) + .map_err(|_| Error("input_file_unavailable"))?; + // Check the opened file, not a separate path lookup that can race it. + let meta = file + .metadata() + .map_err(|_| Error("input_file_unavailable"))?; + if !meta.is_file() { + return Err(Error("regular_file_required")); + } + if private + && (meta.mode() & 0o077 != 0 || meta.uid() != rustix::process::geteuid().as_raw()) + { + return Err(Error("unsafe_encryption_key_permissions")); + } + let mut bytes = Zeroizing::new(Vec::with_capacity(limit + 1)); + file.take(limit as u64 + 1) + .read_to_end(&mut bytes) + .map_err(|_| Error("input_file_unavailable"))?; + if bytes.len() > limit { + return Err(Error("input_file_too_large")); + } + Ok(bytes) + } + + fn write_new(path: &Path, bytes: &[u8]) -> Result<()> { + if let Some(parent) = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + { + DirBuilder::new() + .recursive(true) + .mode(0o700) + .create(parent) + .map_err(|_| Error("output_directory_unavailable"))?; + } + let mut file = OpenOptions::new() + .write(true) + .create_new(true) + .mode(0o600) + .open(path) + .map_err(|_| Error("output_must_be_new_file"))?; + if file.write_all(bytes).and_then(|_| file.sync_all()).is_err() { + let _ = fs::remove_file(path); + return Err(Error("output_write_failed")); + } + Ok(()) + } +} diff --git a/rust/crates/msg-crypto/src/sealed_box.rs b/rust/crates/msg-crypto/src/sealed_box.rs new file mode 100644 index 00000000..2104b71d --- /dev/null +++ b/rust/crates/msg-crypto/src/sealed_box.rs @@ -0,0 +1,316 @@ +//! The existing `msg-x25519-v1` envelope: X25519, HKDF-SHA256 and AES-256-GCM. +//! Encryption keys are independent of signing keys. This module performs no I/O. +use std::collections::BTreeMap; + +use aes_gcm::{aead::AeadInPlace, Aes256Gcm, KeyInit, Nonce}; +use hkdf::Hkdf; +use msg_core::{b64, unb64, Error, Json, Result}; +use rand_core::{OsRng, RngCore}; +use sha2::Sha256; +use x25519_dalek::{PublicKey, SharedSecret, StaticSecret}; +use zeroize::Zeroizing; + +pub const FORMAT: &str = "msg-x25519-v1"; +/// Matches the Python client's small-file encryption limit. +pub const MAX_PLAINTEXT_BYTES: usize = 512 * 1024; +/// The historical download limit; decryption does not narrow it to 512 KiB. +pub const MAX_ENVELOPE_BYTES: usize = 1024 * 1024; + +fn random_bytes() -> Result> { + let mut bytes = Zeroizing::new([0; N]); + OsRng + .try_fill_bytes(&mut bytes[..]) + .map_err(|_| Error("random_source_unavailable"))?; + Ok(bytes) +} + +/// Generate an independent encryption key. The caller must keep the private +/// bytes local and persist them securely; the returned buffer clears on drop. +pub fn generate_key() -> Result<(Zeroizing<[u8; 32]>, [u8; 32])> { + let private = random_bytes()?; + let public = PublicKey::from(&StaticSecret::from(*private)).to_bytes(); + Ok((private, public)) +} + +fn derive( + shared: &SharedSecret, + salt: &[u8; 32], + recipient: &[u8; 32], + ephemeral: &[u8; 32], +) -> Result> { + let mut context = b"msg-keystore-v1\0".to_vec(); + context.extend_from_slice(recipient); + context.extend_from_slice(ephemeral); + let mut key = Zeroizing::new([0; 32]); + Hkdf::::new(Some(salt), shared.as_bytes()) + .expand(&context, &mut key[..]) + .map_err(|_| Error("encryption_failed"))?; + Ok(key) +} + +/// Encrypt using fresh OS randomness for each ephemeral key, salt and nonce. +pub fn encrypt(plaintext: &[u8], recipient_public: &[u8]) -> Result> { + if plaintext.len() > MAX_PLAINTEXT_BYTES { + return Err(Error("use_streaming_age_or_openpgp")); + } + let recipient: [u8; 32] = recipient_public + .try_into() + .map_err(|_| Error("invalid_encryption_input"))?; + let ephemeral = StaticSecret::from(*random_bytes::<32>()?); + let public = PublicKey::from(&ephemeral).to_bytes(); + let shared = ephemeral.diffie_hellman(&PublicKey::from(recipient)); + if !shared.was_contributory() { + return Err(Error("invalid_encryption_recipient")); + } + let salt = random_bytes::<32>()?; + let nonce = random_bytes::<12>()?; + let key = derive(&shared, &salt, &recipient, &public)?; + let mut header = BTreeMap::from([ + ("format".to_owned(), Json::string(FORMAT)), + ("recipient".to_owned(), Json::string(b64(&recipient))), + ("ephemeral".to_owned(), Json::string(b64(&public))), + ("salt".to_owned(), Json::string(b64(&salt[..]))), + ("nonce".to_owned(), Json::string(b64(&nonce[..]))), + ]); + let aad = Json::from_object(header.clone()).canonical()?; + let mut ciphertext = Zeroizing::new(plaintext.to_vec()); + Aes256Gcm::new_from_slice(&key[..]) + .map_err(|_| Error("encryption_failed"))? + .encrypt_in_place(&Nonce::from(*nonce), aad.as_bytes(), &mut *ciphertext) + .map_err(|_| Error("encryption_failed"))?; + header.insert("ciphertext".to_owned(), Json::string(b64(&ciphertext))); + Ok(Json::from_object(header).canonical()?.into_bytes()) +} + +struct Envelope { + recipient: [u8; 32], + ephemeral: [u8; 32], + salt: [u8; 32], + nonce: [u8; 12], + ciphertext: Vec, + aad: Vec, +} + +fn bytes(value: &Json, limit: usize) -> Result> { + unb64(value.as_str().map_err(|_| Error("invalid_base64"))?, limit) +} + +fn fixed(value: &Json) -> Result<[u8; N]> { + bytes(value, N)? + .try_into() + .map_err(|_| Error("invalid_ciphertext_envelope")) +} + +fn parse(content: &[u8]) -> Result { + if content.len() > MAX_ENVELOPE_BYTES { + return Err(Error("ciphertext_envelope_too_large")); + } + let text = std::str::from_utf8(content).map_err(|_| Error("invalid_json"))?; + let mut fields = Json::parse(text)? + .into_object() + .map_err(|_| Error("invalid_ciphertext_envelope"))?; + let names = [ + "format", + "recipient", + "ephemeral", + "salt", + "nonce", + "ciphertext", + ]; + if fields.len() != names.len() || names.iter().any(|name| !fields.contains_key(*name)) { + return Err(Error("invalid_ciphertext_envelope")); + } + if fields["format"].as_str().ok() != Some(FORMAT) { + return Err(Error("invalid_ciphertext_format")); + } + let recipient = fixed(&fields["recipient"])?; + let ephemeral = fixed(&fields["ephemeral"])?; + let salt = fixed(&fields["salt"])?; + let nonce = fixed(&fields["nonce"])?; + let ciphertext = bytes(&fields["ciphertext"], MAX_ENVELOPE_BYTES)?; + if ciphertext.len() < 16 { + return Err(Error("invalid_ciphertext_envelope")); + } + fields.remove("ciphertext"); + Ok(Envelope { + recipient, + ephemeral, + salt, + nonce, + ciphertext, + aad: Json::from_object(fields).canonical()?.into_bytes(), + }) +} + +/// Check bounded envelope shape only. This does not authenticate ciphertext or +/// grant access to a resource; only the recipient can decrypt its contents. +pub fn validate_envelope(content: &[u8]) -> Result<()> { + parse(content).map(|_| ()) +} + +/// Authenticate the entire envelope before returning plaintext. Owned secret +/// buffers clear on drop; callers remain responsible for their input buffers. +pub fn decrypt(content: &[u8], private: &[u8]) -> Result>> { + let envelope = parse(content)?; + let secret = StaticSecret::from( + <[u8; 32]>::try_from(private).map_err(|_| Error("invalid_encryption_key"))?, + ); + let recipient = PublicKey::from(&secret).to_bytes(); + if recipient != envelope.recipient { + return Err(Error("wrong_decryption_recipient")); + } + let shared = secret.diffie_hellman(&PublicKey::from(envelope.ephemeral)); + if !shared.was_contributory() { + return Err(Error("ciphertext_authentication_failed")); + } + let key = derive(&shared, &envelope.salt, &recipient, &envelope.ephemeral)?; + let mut plaintext = Zeroizing::new(envelope.ciphertext); + Aes256Gcm::new_from_slice(&key[..]) + .map_err(|_| Error("ciphertext_authentication_failed"))? + .decrypt_in_place(&Nonce::from(envelope.nonce), &envelope.aad, &mut *plaintext) + .map_err(|_| Error("ciphertext_authentication_failed"))?; + Ok(plaintext) +} + +#[cfg(test)] +mod tests { + use super::*; + + // Generated independently with Python cryptography 50.0.2, using recipient + // private [7;32], ephemeral private [42;32], salt [3;32] and nonce [4;12]. + const PYTHON_ENVELOPE: &str = r#"{"ciphertext":"8QQLJE6wLzgixfIFhEhzo6WupV7befWC_EoMxgPAwx5TIQ","ephemeral":"B6r_Pp_BZydVRPTDpqF82Dfy7G54zYpXsePfs8wDWnY","format":"msg-x25519-v1","nonce":"BAQEBAQEBAQEBAQE","recipient":"E75P6uryBMf9M1j8nAByGIHRdCeBKCJ-xnTzf3_pe20","salt":"AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM"}"#; + + fn fixture() -> BTreeMap { + Json::parse(PYTHON_ENVELOPE).unwrap().into_object().unwrap() + } + + fn encode(fields: BTreeMap) -> Vec { + Json::from_object(fields).canonical().unwrap().into_bytes() + } + + #[test] + fn decrypts_python_vector_and_noncanonical_json() { + let expected = b"Rust\0Python\xff\xe8\x87\xaa\xe7\x94\xb1"; + assert_eq!( + &*decrypt(PYTHON_ENVELOPE.as_bytes(), &[7; 32]).unwrap(), + expected + ); + // Whitespace and member order are not part of the authenticated header. + let members = fixture() + .iter() + .rev() + .map(|(name, value)| format!(" \"{name}\": {}", value.canonical().unwrap())) + .collect::>() + .join(",\n"); + let reordered = format!("{{\n{members}\n}}"); + assert_eq!(&*decrypt(reordered.as_bytes(), &[7; 32]).unwrap(), expected); + } + + #[test] + fn random_round_trips_include_empty_binary_and_size_limit() { + let (private, public) = generate_key().unwrap(); + let (other_private, other_public) = generate_key().unwrap(); + assert_ne!(&*private, &*other_private); + assert_ne!(public, other_public); + for plaintext in [ + vec![], + b"\0\xff\n\xe4\xb8\xad\xe6\x96\x87".to_vec(), + vec![42; MAX_PLAINTEXT_BYTES], + ] { + let sealed = encrypt(&plaintext, &public).unwrap(); + validate_envelope(&sealed).unwrap(); + assert!(sealed.len() <= MAX_ENVELOPE_BYTES); + assert_eq!(&*decrypt(&sealed, &private[..]).unwrap(), &plaintext); + } + let first = parse(&encrypt(b"same", &public).unwrap()).unwrap(); + let second = parse(&encrypt(b"same", &public).unwrap()).unwrap(); + assert_ne!(first.ephemeral, second.ephemeral); + assert_ne!(first.salt, second.salt); + assert_ne!(first.nonce, second.nonce); + } + + #[test] + fn authenticates_recipient_and_every_header_field() { + for name in ["recipient", "ephemeral", "salt", "nonce", "ciphertext"] { + let mut fields = fixture(); + let mut value = bytes(&fields[name], MAX_ENVELOPE_BYTES).unwrap(); + value[0] ^= 1; + fields.insert(name.to_owned(), Json::string(b64(&value))); + let result = decrypt(&encode(fields), &[7; 32]); + assert_eq!( + result.unwrap_err(), + Error(if name == "recipient" { + "wrong_decryption_recipient" + } else { + "ciphertext_authentication_failed" + }) + ); + } + assert_eq!( + decrypt(PYTHON_ENVELOPE.as_bytes(), &[9; 32]).unwrap_err(), + Error("wrong_decryption_recipient") + ); + } + + #[test] + fn rejects_low_order_keys_in_both_directions() { + let mut identity = [0; 32]; + identity[0] = 1; + let mut modulus = [255; 32]; + modulus[0] = 237; + modulus[31] = 127; + for key in [[0; 32], identity, modulus] { + assert_eq!( + encrypt(b"secret", &key).unwrap_err(), + Error("invalid_encryption_recipient") + ); + let mut fields = fixture(); + fields.insert("ephemeral".to_owned(), Json::string(b64(&key))); + assert_eq!( + decrypt(&encode(fields), &[7; 32]).unwrap_err(), + Error("ciphertext_authentication_failed") + ); + } + } + + #[test] + fn rejects_ambiguous_malformed_and_oversized_envelopes() { + for raw in ["[]", "null", "{}", "{\"format\":0,\"format\":1}"] { + assert!(validate_envelope(raw.as_bytes()).is_err()); + } + let duplicate = PYTHON_ENVELOPE.replacen('{', "{\"\\u0066ormat\":\"msg-x25519-v1\",", 1); + assert_eq!( + validate_envelope(duplicate.as_bytes()).unwrap_err(), + Error("duplicate_json_key") + ); + for (name, value) in [ + ("extra", Json::string("ignored")), + ("format", Json::string("other-v1")), + ("recipient", Json::empty_array()), + ("ephemeral", Json::string("invalid=")), + ("salt", Json::string(b64(&[0; 31]))), + ("nonce", Json::string(b64(&[0; 13]))), + ("ciphertext", Json::string(b64(&[0; 15]))), + ] { + let mut fields = fixture(); + fields.insert(name.to_owned(), value); + assert!(validate_envelope(&encode(fields)).is_err()); + } + let mut missing = fixture(); + missing.remove("nonce"); + assert!(validate_envelope(&encode(missing)).is_err()); + assert_eq!( + encrypt(&vec![0; MAX_PLAINTEXT_BYTES + 1], &[1; 32]).unwrap_err(), + Error("use_streaming_age_or_openpgp") + ); + assert_eq!( + validate_envelope(&vec![b' '; MAX_ENVELOPE_BYTES + 1]).unwrap_err(), + Error("ciphertext_envelope_too_large") + ); + assert_eq!( + decrypt(PYTHON_ENVELOPE.as_bytes(), &[7; 31]).unwrap_err(), + Error("invalid_encryption_key") + ); + assert!(validate_envelope(&[255]).is_err()); + } +} diff --git a/rust/tests/test_crypto_parity.py b/rust/tests/test_crypto_parity.py new file mode 100644 index 00000000..9d521df9 --- /dev/null +++ b/rust/tests/test_crypto_parity.py @@ -0,0 +1,243 @@ +"""Exercise the native encryption CLI against the actual Python implementation. + +All keys and files are disposable. No server, account or network is used. +Usage: PYTHONPATH=src python rust/tests/test_crypto_parity.py --binary PATH +""" + +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import tempfile +from pathlib import Path + +from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey + +from msg.client_secrets import encryption_keygen, write_private +from msg.core.codec import b64, canonical, unb64 +from msg.security.sealed_box import FORMAT, decrypt, encrypt, validate_envelope + +REPORT = [] +PLAINTEXT_LIMIT = 524288 +ENVELOPE_LIMIT = 1048576 + + +def check(condition, name): + if not condition: + raise AssertionError(name) + REPORT.append(name) + + +def native(binary, *arguments, rejected=False): + result = subprocess.run( + [str(binary), *(str(arg) for arg in arguments)], + capture_output=True, + timeout=15, + check=False, + ) + if rejected: + if result.returncode <= 0 or b'panicked at' in result.stderr: + raise AssertionError('invalid input was accepted or crashed the native process') + elif result.returncode != 0: + raise AssertionError(result.stderr.decode(errors='replace')) + return result.stdout + + +def python_rejects(content, private): + try: + decrypt(content, private) + except ValueError, TypeError: + return True + return False + + +def interoperate(binary, root): + native_key = root / 'rust.key' + pair = json.loads(native(binary, 'keygen', native_key)) + private = native_key.read_bytes() + public = X25519PrivateKey.from_private_bytes(private).public_key().public_bytes_raw() + check(len(private) == 32, 'keygen-raw-private-key') + check(pair['format'] == FORMAT and pair['recipient'] == b64(public), 'keygen-python-public-key') + check(pair['private_key_file'] == str(native_key), 'keygen-output-path') + check(native_key.stat().st_mode & 0o777 == 0o600, 'keygen-private-mode') + + python_key = root / 'python.key' + python_pair = encryption_keygen(python_key) + payloads = [b'', '自由,加密\0\n'.encode() + bytes(range(256)), bytes(range(256)) * 2048] + for index, plaintext in enumerate(payloads): + source = root / f'source-{index}' + source.write_bytes(plaintext) + py_envelope = root / f'python-{index}.sealed' + py_envelope.write_bytes(encrypt(plaintext, public)) + output = root / f'opened-{index}' + native(binary, 'decrypt', native_key, py_envelope, output) + check(output.read_bytes() == plaintext, f'python-to-rust-{index}') + check(output.stat().st_mode & 0o777 == 0o600, f'plaintext-private-mode-{index}') + + rust_envelope = root / f'rust-{index}.sealed' + native(binary, 'encrypt', python_pair['recipient'], source, rust_envelope) + sealed = rust_envelope.read_bytes() + check(decrypt(sealed, python_key.read_bytes()) == plaintext, f'rust-to-python-{index}') + check(canonical(validate_envelope(sealed)) == sealed, f'canonical-envelope-{index}') + check(rust_envelope.stat().st_mode & 0o777 == 0o600, f'ciphertext-private-mode-{index}') + + another = root / 'randomized.sealed' + native(binary, 'encrypt', python_pair['recipient'], source, another) + check(another.read_bytes() != sealed, 'fresh-encryption-randomness') + check( + decrypt(another.read_bytes(), python_key.read_bytes()) == plaintext, + 'fresh-envelope-opens', + ) + + # Python historically allowed larger plaintext; only new encryption is capped. + historical = b'h' * (600 * 1024) + legacy_envelope, legacy_output = root / 'historical.sealed', root / 'historical-opened' + legacy_envelope.write_bytes(encrypt(historical, public)) + native(binary, 'decrypt', native_key, legacy_envelope, legacy_output) + check(legacy_output.read_bytes() == historical, 'historical-envelope-above-plaintext-limit') + return native_key, private, public + + +def rejected_envelopes(binary, root, key, private, public): + envelope = encrypt(b'disposable secret', public) + value = validate_envelope(envelope) + invalid = {'invalid-json': b'{', 'duplicate-field': b'{"format":"x",' + envelope[1:]} + for name in ['recipient', 'ephemeral', 'salt', 'nonce', 'ciphertext']: + raw = bytearray(unb64(value[name])) + raw[0] ^= 1 + invalid['changed-' + name] = canonical(value | {name: b64(raw)}) + invalid.update({ + 'unknown-format': canonical(value | {'format': 'unknown'}), + 'unknown-field': canonical(value | {'extra': True}), + 'missing-field': canonical({name: item for name, item in value.items() if name != 'salt'}), + 'short-nonce': canonical(value | {'nonce': b64(bytes(11))}), + 'short-tag': canonical(value | {'ciphertext': b64(bytes(15))}), + 'low-order-ephemeral': canonical(value | {'ephemeral': b64(bytes(32))}), + }) + for name, content in invalid.items(): + source, output = root / (name + '.sealed'), root / name + source.write_bytes(content) + check(python_rejects(content, private), 'python-rejects-' + name) + native(binary, 'decrypt', key, source, output, rejected=True) + check(not output.exists(), 'rust-rejects-without-output-' + name) + + valid = root / 'valid.sealed' + valid.write_bytes(envelope) + formatted, formatted_output = root / 'formatted.sealed', root / 'formatted-opened' + formatted.write_text(json.dumps(dict(reversed(value.items())), indent=2), encoding='utf-8') + native(binary, 'decrypt', key, formatted, formatted_output) + check(formatted_output.read_bytes() == b'disposable secret', 'header-order-and-whitespace') + wrong_key = root / 'wrong.key' + encryption_keygen(wrong_key) + output = root / 'wrong-recipient' + native(binary, 'decrypt', wrong_key, valid, output, rejected=True) + check(not output.exists(), 'wrong-recipient-no-plaintext') + + source = root / 'plaintext' + source.write_bytes(b'never publish this for a low-order key') + output = root / 'low-order-recipient' + native(binary, 'encrypt', b64(bytes(32)), source, output, rejected=True) + check(not output.exists(), 'low-order-recipient-no-ciphertext') + return valid, source + + +def file_boundaries(binary, root, key, private, public, envelope, source): + read_fd, write_fd = os.pipe() + os.close(read_fd) + saved_key = root / 'closed-stdout.key' + try: + result = subprocess.run( + [str(binary), 'keygen', str(saved_key)], + stdout=write_fd, + stderr=subprocess.PIPE, + timeout=15, + check=False, + ) + finally: + os.close(write_fd) + check( + result.returncode == 1 + and b'panicked at' not in result.stderr + and len(saved_key.read_bytes()) == 32, + 'closed-stdout-retains-private-key-without-panic', + ) + native(binary, 'keygen', key, rejected=True) + check(key.read_bytes() == private, 'keygen-never-overwrites-private-key') + existing = root / 'existing' + existing.write_bytes(b'keep existing bytes') + for operation, identity, content in [ + ('encrypt', b64(public), source), + ('decrypt', key, envelope), + ]: + native(binary, operation, identity, content, existing, rejected=True) + check(existing.read_bytes() == b'keep existing bytes', operation + '-never-overwrites') + link = root / (operation + '-symlink') + link.symlink_to(existing) + native(binary, operation, identity, content, link, rejected=True) + check( + link.is_symlink() and existing.read_bytes() == b'keep existing bytes', + operation + '-symlink-intact', + ) + + linked_key = root / 'linked.key' + linked_key.symlink_to(key) + output = root / 'linked-key-output' + native(binary, 'decrypt', linked_key, envelope, output, rejected=True) + check(not output.exists(), 'symlink-private-key-rejected') + + key.chmod(0o644) + try: + output = root / 'unsafe-key-output' + native(binary, 'decrypt', key, envelope, output, rejected=True) + check(not output.exists(), 'publicly-readable-key-rejected') + finally: + key.chmod(0o600) + for name, content in [('short', private[:-1]), ('long', private + b'x')]: + bad_key, output = root / (name + '.key'), root / (name + '-key-output') + write_private(bad_key, content) + native(binary, 'decrypt', bad_key, envelope, output, rejected=True) + check(not output.exists(), name + '-private-key-rejected') + + for operation, identity, size in [ + ('encrypt', b64(public), PLAINTEXT_LIMIT + 1), + ('decrypt', key, ENVELOPE_LIMIT + 1), + ]: + oversized, output = root / (operation + '-oversized'), root / (operation + '-bounded') + content = b'x' * size if operation == 'encrypt' else envelope.read_bytes() + oversized.write_bytes(content + b' ' * (size - len(content))) + native(binary, operation, identity, oversized, output, rejected=True) + check(not output.exists(), operation + '-size-bound-no-output') + + fifo = root / 'fifo.key' + os.mkfifo(fifo, 0o600) + output = root / 'fifo-key-output' + native(binary, 'decrypt', fifo, envelope, output, rejected=True) + check(not output.exists(), 'nonregular-private-key-rejected-without-blocking') + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--binary', required=True, type=Path) + parser.add_argument('--report', type=Path) + args = parser.parse_args() + binary = args.binary.resolve() + with tempfile.TemporaryDirectory(prefix='msg-crypto-parity-') as name: + root = Path(name) + key, private, public = interoperate(binary, root) + envelope, source = rejected_envelopes(binary, root, key, private, public) + file_boundaries(binary, root, key, private, public, envelope, source) + report = { + 'checks': len(REPORT), + 'passed': REPORT, + 'scope': 'native local encryption; no server', + } + if args.report: + args.report.parent.mkdir(parents=True, exist_ok=True) + args.report.write_text(json.dumps(report, indent=2) + '\n', encoding='utf-8') + print(json.dumps(report)) + + +if __name__ == '__main__': + main() From e6fdbea41fd6374a1de3a56bdf2d6dbe7d37fef6 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Thu, 22 Oct 2026 06:22:16 +0800 Subject: [PATCH 02/13] build(crypto): lock verified dependencies and document Rust migration entry point --- .github/workflows/rust-crypto.yml | 6 +- README.md | 5 + README.zh-CN.md | 4 + rust/Cargo.lock | 157 +++++++++++++++++++++++++++++- rust/MIGRATION.md | 7 +- 5 files changed, 174 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust-crypto.yml b/.github/workflows/rust-crypto.yml index ffaa326c..78cd8bb3 100644 --- a/.github/workflows/rust-crypto.yml +++ b/.github/workflows/rust-crypto.yml @@ -32,9 +32,9 @@ jobs: if: ${{ !cancelled() }} working-directory: rust run: | - cargo +1.85.0 test -p msg-crypto - cargo +1.85.0 clippy -p msg-crypto --all-targets -- -D warnings - cargo +1.85.0 build -p msg-crypto + cargo +1.85.0 test --locked -p msg-crypto + cargo +1.85.0 clippy --locked -p msg-crypto --all-targets -- -D warnings + cargo +1.85.0 build --locked -p msg-crypto - name: Python and Rust interoperability env: PYTHONPATH: src diff --git a/README.md b/README.md index 50f2e0f3..878c2edf 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,11 @@ English · [简体中文](README.zh-CN.md) The repository is [**msg**](https://github.com/TokenNotIncluded/msg). **`msg`** is the client command and **`msgd`** is the server command; the PyPI distribution remains **`msgctl`**. See the [documentation guide](docs/README.md) for current manuals and historical evidence. +The [Rust rewrite](rust/README.md) includes `msg-crypto`, an offline tool for +key generation, encryption and decryption compatible with existing Python +encrypted files. The production `msg` and `msgd` entry points remain Python +until their native replacements pass the [migration gates](rust/MIGRATION.md). + ![MSG terminal walkthrough: connect, read, post, reply and create a Git repository](docs/media/msg-terminal-demo.gif) *Illustrated command walkthrough with sample content, not a recording of live messages. [Commands and demo source](docs/TERMINAL_DEMO.md) · [Watch the video on X](https://x.com/LIghtJUNction_x/status/2105389700534137061).* diff --git a/README.zh-CN.md b/README.zh-CN.md index c8b49585..72cea5f6 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -19,6 +19,10 @@ 现行手册和历史依据见[文档导航](docs/README.md)。 +[Rust 重写](rust/README.md)已提供独立的 `msg-crypto`:本地生成密钥、加密和解密, +与现有 Python 加密文件互通。生产入口 `msg`、`msgd` 仍使用 Python, +逐项通过[迁移验收](rust/MIGRATION.md)后再切换。 + ![MSG 终端演示:连接、读帖、发帖、回复和创建 Git 仓库](docs/media/msg-terminal-demo.gif) *动图为命令流程示意,使用演示内容,没有向线上发消息。[命令和动图生成方式](docs/TERMINAL_DEMO.md) · [在 X 看视频](https://x.com/LIghtJUNction_x/status/2105389700534137061)。* diff --git a/rust/Cargo.lock b/rust/Cargo.lock index afc9bed7..47973ea8 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,6 +2,41 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -132,6 +167,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + [[package]] name = "cmov" version = "0.5.4" @@ -191,6 +236,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -203,6 +249,15 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "ctutils" version = "0.4.2" @@ -257,6 +312,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "crypto-common 0.1.7", + "subtle", ] [[package]] @@ -440,6 +496,16 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -458,6 +524,24 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "hmac" version = "0.13.0" @@ -500,6 +584,15 @@ dependencies = [ "cc", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "itoa" version = "1.0.18" @@ -606,10 +699,18 @@ dependencies = [ name = "msg-crypto" version = "0.1.0" dependencies = [ + "aes-gcm", "ed25519-dalek", + "hkdf", + "libc", "msg-core", + "rand_core 0.6.4", + "rustix", "serde", + "serde_json", "sha2 0.10.9", + "x25519-dalek", + "zeroize", ] [[package]] @@ -706,6 +807,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.75" @@ -820,6 +927,18 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "postgres" version = "0.19.12" @@ -856,7 +975,7 @@ dependencies = [ "byteorder", "bytes", "fallible-iterator 0.2.0", - "hmac", + "hmac 0.13.0", "md-5", "memchr", "rand", @@ -1302,6 +1421,16 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -1511,6 +1640,18 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "zerocopy" version = "0.8.59" @@ -1536,3 +1677,17 @@ name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] diff --git a/rust/MIGRATION.md b/rust/MIGRATION.md index 60aad44c..14594945 100644 --- a/rust/MIGRATION.md +++ b/rust/MIGRATION.md @@ -31,13 +31,18 @@ decryption, bounded envelope validation and a Unix `keygen/encrypt/decrypt` CLI. It keeps Python's wire format and safe local-file behavior without a network or new account framework. Python remains the compatibility reference. +The encryption acceptance suite covers 7 Rust tests and 64 Python/Rust checks, +including both encryption directions, tampering, bounds and safe local files. +The scoped CI runs Rust 1.85 with locked dependencies, Python 3.15, formatting, +Ruff and Clippy with warnings denied. These are cryptographic slice checks. + This extends the cryptographic foundation; it does not complete identity issuance, password-wrapped signing keys, remote keystore operations or a native server. The fixed completed/remaining count therefore stays **5 / 6**. ## Slice 5 acceptance and boundaries -- 14 new native content tests; current workspace: 46 unit tests and 5 doc tests. +- 14 new native content tests; at slice 5 acceptance: 46 unit tests and 5 doc tests. - 170 Python/Rust content checks per debug/release profile. These include actual shared storage, 64 KiB boundary ranges, hard-link retention, canonical revision manifests and ancestry, pin interoperability, corrupted objects, ignored Git From 228326ac0f476865776abc22b6cbd86c03f91aa4 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Thu, 22 Oct 2026 06:23:19 +0800 Subject: [PATCH 03/13] docs: specify the Python encryption compatibility format --- README.md | 4 ++-- README.zh-CN.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 878c2edf..7a7fb869 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,8 @@ English · [简体中文](README.zh-CN.md) The repository is [**msg**](https://github.com/TokenNotIncluded/msg). **`msg`** is the client command and **`msgd`** is the server command; the PyPI distribution remains **`msgctl`**. See the [documentation guide](docs/README.md) for current manuals and historical evidence. The [Rust rewrite](rust/README.md) includes `msg-crypto`, an offline tool for -key generation, encryption and decryption compatible with existing Python -encrypted files. The production `msg` and `msgd` entry points remain Python +key generation, encryption and decryption compatible with Python's +`msg-x25519-v1` files. The production `msg` and `msgd` entry points remain Python until their native replacements pass the [migration gates](rust/MIGRATION.md). ![MSG terminal walkthrough: connect, read, post, reply and create a Git repository](docs/media/msg-terminal-demo.gif) diff --git a/README.zh-CN.md b/README.zh-CN.md index 72cea5f6..1e8c2420 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -20,7 +20,7 @@ 现行手册和历史依据见[文档导航](docs/README.md)。 [Rust 重写](rust/README.md)已提供独立的 `msg-crypto`:本地生成密钥、加密和解密, -与现有 Python 加密文件互通。生产入口 `msg`、`msgd` 仍使用 Python, +与 Python 的 `msg-x25519-v1` 加密文件互通。生产入口 `msg`、`msgd` 仍使用 Python, 逐项通过[迁移验收](rust/MIGRATION.md)后再切换。 ![MSG 终端演示:连接、读帖、发帖、回复和创建 Git 仓库](docs/media/msg-terminal-demo.gif) From 7db465efcfcf56c49718fb5df3cbe5ecc5551231 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Thu, 22 Oct 2026 06:48:53 +0800 Subject: [PATCH 04/13] build(msgd): export complete Python service contracts for native migration --- .github/workflows/rust-server.yml | 31 ++++++++++++++++ rust/tools/export_catalog.py | 61 +++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 .github/workflows/rust-server.yml create mode 100644 rust/tools/export_catalog.py diff --git a/.github/workflows/rust-server.yml b/.github/workflows/rust-server.yml new file mode 100644 index 00000000..0a0fe979 --- /dev/null +++ b/.github/workflows/rust-server.yml @@ -0,0 +1,31 @@ +name: Rust daemon +on: + push: + paths: ['rust/**', '.github/workflows/rust-server.yml'] + pull_request: + paths: ['rust/**', '.github/workflows/rust-server.yml'] + workflow_dispatch: +permissions: + contents: read +jobs: + reference: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: '3.15' + - name: Install reference dependencies + run: python -m pip install 'cryptography==46.0.3' 'httpx>=0.28,<1' 'jsonschema>=4.25,<5' 'referencing>=0.36,<1' + - name: Export service contracts + env: + PYTHONPATH: src + run: python rust/tools/export_catalog.py --output rust/artifacts/catalog.json + - uses: actions/upload-artifact@v7 + with: + name: daemon-reference-${{ github.sha }} + path: rust/artifacts/catalog.json + if-no-files-found: error diff --git a/rust/tools/export_catalog.py b/rust/tools/export_catalog.py new file mode 100644 index 00000000..d844719d --- /dev/null +++ b/rust/tools/export_catalog.py @@ -0,0 +1,61 @@ +"""Export the Python compatibility reference without opening storage or keys.""" + +import argparse +import inspect +import json +from pathlib import Path +from types import SimpleNamespace + +from msg.application import Application +from msg.config_contracts import CONFIGURATION_FIELDS +from msg.core.codec import wire +from msg.plugins import BUILTINS +from msg.security.capabilities import BASE_FAMILIES + + +def catalog(): + app = Application(SimpleNamespace(server=SimpleNamespace(plugins=BUILTINS))) + registry = app.registry + operations = [] + schemas = {} + for spec in registry.operations(): + row = registry.describe(spec) + for ref in (spec.input_schema, spec.output_schema): + schemas[ref.id] = registry.schema(ref) + row['handler'] = spec.handler.__module__ + ':' + spec.handler.__qualname__ + row['requirements'] = ( + spec.requirements.__module__ + ':' + spec.requirements.__qualname__ + ) + if spec.requirements.__qualname__ == 'requirement..requirements': + closure = inspect.getclosurevars(spec.requirements).nonlocals + row['access'] = {key: closure[key] for key in ('argument', 'check', 'default')} + operations.append(row) + return { + 'version': 1, + 'plugins': list(BUILTINS), + 'operations': operations, + 'schemas': schemas, + 'capabilities': wire(registry.capabilities()), + 'resource_types': wire(registry.resource_types()), + 'base_grants': wire(app.base_grants()), + 'primary_ceiling': wire(app.primary_ceiling()), + 'temporary_ceiling': wire(app.temporary_ceiling()), + 'base_families': sorted(BASE_FAMILIES), + 'configuration': CONFIGURATION_FIELDS, + } + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--output', type=Path, required=True) + args = parser.parse_args() + result = catalog() + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text( + json.dumps(result, ensure_ascii=False, sort_keys=True, separators=(',', ':')) + '\n' + ) + print(json.dumps({'operations': len(result['operations']), 'schemas': len(result['schemas'])})) + + +if __name__ == '__main__': + main() From f98525e80d12afcc03a009e9555e602eb462d672 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Thu, 22 Oct 2026 06:52:31 +0800 Subject: [PATCH 05/13] feat(msgd): add native identity lifecycle, recovery transactions and durable worker --- .github/workflows/rust-server.yml | 28 +- rust/Cargo.toml | 12 +- rust/crates/msg-crypto/src/age_recipient.rs | 139 ++ rust/crates/msg-crypto/src/lib.rs | 1 + rust/crates/msg-identity/Cargo.toml | 3 + .../crates/msg-identity/src/authentication.rs | 18 + rust/crates/msg-identity/src/browser.rs | 567 +++++++ rust/crates/msg-identity/src/certificates.rs | 24 + rust/crates/msg-identity/src/lib.rs | 4 + rust/crates/msg-identity/src/lifecycle.rs | 1369 +++++++++++++++++ rust/crates/msg-identity/src/mutations.rs | 70 + rust/crates/msg-identity/src/sessions.rs | 8 +- .../msg-identity/src/worker_authority.rs | 222 +++ .../msg-identity/tests/browser_lifecycle.rs | 417 +++++ rust/crates/msg-storage/src/content/files.rs | 2 +- rust/crates/msg-storage/src/content/lfs.rs | 367 +++++ .../msg-storage/src/content/lfs/tests.rs | 280 ++++ rust/crates/msg-storage/src/content/mod.rs | 7 +- .../msg-storage/src/content/recovery.rs | 423 +++++ .../msg-storage/src/content/recovery/tests.rs | 373 +++++ rust/crates/msg-storage/src/jobs.rs | 174 +++ rust/crates/msg-storage/src/lib.rs | 1 + .../msg-storage/src/postgres/identity.rs | 359 +++++ rust/crates/msg-storage/src/postgres/jobs.rs | 78 + rust/crates/msg-storage/src/postgres/mod.rs | 3 + rust/crates/msg-storage/src/records.rs | 6 + rust/crates/msg-worker/Cargo.toml | 25 + rust/crates/msg-worker/src/delivery.rs | 576 +++++++ rust/crates/msg-worker/src/lib.rs | 643 ++++++++ rust/crates/msg-worker/src/mail.rs | 496 ++++++ rust/crates/msg-worker/src/outbound.rs | 464 ++++++ rust/crates/msg-worker/src/tests.rs | 209 +++ 32 files changed, 7361 insertions(+), 7 deletions(-) create mode 100644 rust/crates/msg-crypto/src/age_recipient.rs create mode 100644 rust/crates/msg-identity/src/browser.rs create mode 100644 rust/crates/msg-identity/src/lifecycle.rs create mode 100644 rust/crates/msg-identity/src/mutations.rs create mode 100644 rust/crates/msg-identity/src/worker_authority.rs create mode 100644 rust/crates/msg-identity/tests/browser_lifecycle.rs create mode 100644 rust/crates/msg-storage/src/content/lfs.rs create mode 100644 rust/crates/msg-storage/src/content/lfs/tests.rs create mode 100644 rust/crates/msg-storage/src/content/recovery.rs create mode 100644 rust/crates/msg-storage/src/content/recovery/tests.rs create mode 100644 rust/crates/msg-storage/src/jobs.rs create mode 100644 rust/crates/msg-storage/src/postgres/identity.rs create mode 100644 rust/crates/msg-storage/src/postgres/jobs.rs create mode 100644 rust/crates/msg-worker/Cargo.toml create mode 100644 rust/crates/msg-worker/src/delivery.rs create mode 100644 rust/crates/msg-worker/src/lib.rs create mode 100644 rust/crates/msg-worker/src/mail.rs create mode 100644 rust/crates/msg-worker/src/outbound.rs create mode 100644 rust/crates/msg-worker/src/tests.rs diff --git a/.github/workflows/rust-server.yml b/.github/workflows/rust-server.yml index 0a0fe979..24c2e730 100644 --- a/.github/workflows/rust-server.yml +++ b/.github/workflows/rust-server.yml @@ -8,6 +8,32 @@ on: permissions: contents: read jobs: + native: + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Install Rust + run: rustup toolchain install 1.85.0 --profile minimal --component rustfmt,clippy + - name: Native tests + working-directory: rust + run: cargo +1.85.0 test --workspace --all-targets + - name: Native lint + if: ${{ !cancelled() }} + working-directory: rust + run: | + cargo +1.85.0 fmt --all -- --check + cargo +1.85.0 clippy --workspace --all-targets -- -D warnings + - uses: actions/upload-artifact@v7 + if: ${{ always() }} + with: + name: daemon-lock-${{ github.sha }} + path: rust/Cargo.lock + if-no-files-found: error reference: runs-on: ubuntu-latest timeout-minutes: 15 @@ -19,7 +45,7 @@ jobs: with: python-version: '3.15' - name: Install reference dependencies - run: python -m pip install 'cryptography==46.0.3' 'httpx>=0.28,<1' 'jsonschema>=4.25,<5' 'referencing>=0.36,<1' + run: python -m pip install 'cryptography==46.0.3' 'httpx>=0.28,<1' 'jsonschema>=4.25,<5' 'referencing>=0.36,<1' 'starlette>=0.50,<2' 'aiohttp>=3.13,<4' 'graphql-core>=3.2.7,<3.3' 'dnspython>=2.8,<3' 'markdown-it-py>=4,<5' 'pygments>=2.19,<3' - name: Export service contracts env: PYTHONPATH: src diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 260dc4e1..fbcde9ba 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/msg-core", "crates/msg-crypto", "crates/msg-protocol", "crates/msg-identity", "crates/msg-storage"] +members = ["crates/msg-core", "crates/msg-crypto", "crates/msg-protocol", "crates/msg-identity", "crates/msg-storage", "crates/msg-worker"] resolver = "2" [workspace.package] @@ -10,13 +10,21 @@ license = "MIT" publish = false [workspace.dependencies] +axum = "=0.8.9" +tokio = { version = "=1.54.0", features = ["rt-multi-thread", "macros", "net", "sync", "signal", "time", "io-util"] } +toml = "=0.8.23" +url = "=2.5.8" +reqwest = { version = "=0.12.28", default-features = false, features = ["blocking", "native-tls"] } +hickory-resolver = { version = "=0.24.4", features = ["system-config", "tokio-runtime"] } +jsonschema = { version = "=0.33.0", default-features = false } aes-gcm = "=0.10.3" base64 = "=0.22.1" chrono = { version = "=0.4.42", default-features = false, features = ["std"] } ed25519-dalek = "=2.2.0" hkdf = "=0.12.4" +hmac = "=0.12.1" rand_core = { version = "=0.6.4", features = ["getrandom"] } -rustix = { version = "=1.1.5", features = ["process"] } +rustix = { version = "=1.1.5", features = ["process", "resource"] } ryu = "=1.0.20" serde = { version = "=1.0.228", features = ["derive"] } serde_json = { version = "=1.0.145", features = ["raw_value", "arbitrary_precision"] } diff --git a/rust/crates/msg-crypto/src/age_recipient.rs b/rust/crates/msg-crypto/src/age_recipient.rs new file mode 100644 index 00000000..16367f02 --- /dev/null +++ b/rust/crates/msg-crypto/src/age_recipient.rs @@ -0,0 +1,139 @@ +//! Standard age X25519 recipient text used by existing identity records. +use msg_core::{Error, Result}; +use sha2::{Digest, Sha256}; +use x25519_dalek::{PublicKey, StaticSecret}; + +const ALPHABET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +fn polymod(values: impl IntoIterator) -> u32 { + let mut check = 1u32; + for value in values { + let top = check >> 25; + check = ((check & 0x1ff_ffff) << 5) ^ u32::from(value); + for (index, generator) in [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3] + .into_iter() + .enumerate() + { + if (top >> index) & 1 == 1 { + check ^= generator; + } + } + } + check +} + +fn prefix() -> Vec { + b"age" + .iter() + .map(|c| c >> 5) + .chain([0]) + .chain(b"age".iter().map(|c| c & 31)) + .collect() +} + +pub fn public_from_recipient(recipient: &str) -> Result<[u8; 32]> { + let invalid = Error("invalid_age_recipient"); + if recipient.len() != 62 || !recipient.starts_with("age1") { + return Err(invalid); + } + let values: Vec = recipient.as_bytes()[4..] + .iter() + .map(|c| { + ALPHABET + .iter() + .position(|v| v == c) + .map(|i| i as u8) + .ok_or(invalid) + }) + .collect::>()?; + if polymod(prefix().into_iter().chain(values.iter().copied())) != 1 { + return Err(invalid); + } + let mut public = [0u8; 32]; + let mut accumulator = 0u32; + let mut bits = 0; + let mut index = 0; + for value in &values[..52] { + accumulator = ((accumulator & 0xfff) << 5) | u32::from(*value); + bits += 5; + if bits >= 8 { + bits -= 8; + public[index] = (accumulator >> bits) as u8; + index += 1; + } + } + if index != 32 || bits != 4 || accumulator & 15 != 0 { + return Err(invalid); + } + // A fixed nonsecret scalar is sufficient to reject every low-order point; + // it is never used for message encryption or as an account identity. + if !StaticSecret::from([42u8; 32]) + .diffie_hellman(&PublicKey::from(public)) + .was_contributory() + { + return Err(invalid); + } + Ok(public) +} + +pub fn recipient_from_public(public: &[u8; 32]) -> String { + let mut values = Vec::with_capacity(58); + let mut accumulator = 0u32; + let mut bits = 0; + for byte in public { + accumulator = ((accumulator & 31) << 8) | u32::from(*byte); + bits += 8; + while bits >= 5 { + bits -= 5; + values.push(((accumulator >> bits) & 31) as u8); + } + } + if bits != 0 { + values.push(((accumulator << (5 - bits)) & 31) as u8); + } + let checksum = polymod( + prefix() + .into_iter() + .chain(values.iter().copied()) + .chain([0; 6]), + ) ^ 1; + for index in (0..6).rev() { + values.push(((checksum >> (5 * index)) & 31) as u8); + } + let mut recipient = String::from("age1"); + recipient.extend(values.into_iter().map(|v| ALPHABET[usize::from(v)] as char)); + recipient +} + +pub fn encryption_key_id(public: &[u8; 32]) -> String { + let mut hash = Sha256::new(); + hash.update(b"msg-encryption-subkey-v1\0"); + hash.update(public); + format!("ek_{:x}", hash.finalize()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn canonical_recipients_reject_checksum_case_padding_and_low_order_points() { + let public = PublicKey::from(&StaticSecret::from([1; 32])).to_bytes(); + let recipient = recipient_from_public(&public); + // Generated by the unchanged Python msg.security.age_keys module. + assert_eq!( + recipient, + "age15nsf9y4k28p83wth93tf7hafhvfajp45d2mge80ems45gz0c5gys3me64l" + ); + assert_eq!( + encryption_key_id(&public), + "ek_69d193dcc1c5da912145b4309f9a14f39fdb88678b3f337166b128187b4a7402" + ); + assert_eq!(public_from_recipient(&recipient).unwrap(), public); + assert!(public_from_recipient(&recipient.to_uppercase()).is_err()); + assert!(public_from_recipient(&recipient_from_public(&[0; 32])).is_err()); + let mut changed = recipient.into_bytes(); + changed[10] = if changed[10] == b'q' { b'p' } else { b'q' }; + assert!(public_from_recipient(std::str::from_utf8(&changed).unwrap()).is_err()); + } +} diff --git a/rust/crates/msg-crypto/src/lib.rs b/rust/crates/msg-crypto/src/lib.rs index 7e07f623..02e3044a 100644 --- a/rust/crates/msg-crypto/src/lib.rs +++ b/rust/crates/msg-crypto/src/lib.rs @@ -1,4 +1,5 @@ //! Native signing and recipient encryption, not authentication or authorization. +pub mod age_recipient; pub mod sealed_box; use ed25519_dalek::{Signer as _, SigningKey, VerifyingKey}; diff --git a/rust/crates/msg-identity/Cargo.toml b/rust/crates/msg-identity/Cargo.toml index f6402dfc..2ea9f166 100644 --- a/rust/crates/msg-identity/Cargo.toml +++ b/rust/crates/msg-identity/Cargo.toml @@ -11,10 +11,13 @@ msg-core = { path = "../msg-core" } msg-crypto = { path = "../msg-crypto" } msg-protocol = { path = "../msg-protocol" } chrono.workspace = true +hmac.workspace = true +rand_core.workspace = true serde.workspace = true serde_json.workspace = true sha2.workspace = true subtle.workspace = true +zeroize.workspace = true [lints] workspace = true diff --git a/rust/crates/msg-identity/src/authentication.rs b/rust/crates/msg-identity/src/authentication.rs index 6c01e7ea..abdf455a 100644 --- a/rust/crates/msg-identity/src/authentication.rs +++ b/rust/crates/msg-identity/src/authentication.rs @@ -51,6 +51,24 @@ pub struct Principal { ceiling: Vec, } impl Principal { + pub(crate) fn from_worker( + actor: String, + subject: String, + credential_id: String, + method: &'static str, + certificates: Vec, + ceiling: Vec, + ) -> Self { + Self { + actor: Some(actor), + subject: Some(subject), + credential_id: Some(credential_id), + method, + certificates, + ceiling, + } + } + pub fn actor(&self) -> Option<&str> { self.actor.as_deref() } diff --git a/rust/crates/msg-identity/src/browser.rs b/rust/crates/msg-identity/src/browser.rs new file mode 100644 index 00000000..28f9f77d --- /dev/null +++ b/rust/crates/msg-identity/src/browser.rs @@ -0,0 +1,567 @@ +//! Signed local-key approval and digest-only browser sessions. +//! +//! Every method runs inside the caller's fenced metadata transaction. A failed +//! method must roll that transaction back. Cookies and returned token secrets +//! are delivery material: they must not enter receipts, audit bodies or logs. +use crate::{ + authentication::{OperationPolicy, Principal}, + models::*, + policy::scope_subset, + require, + sessions::{require_binding, require_source, BrowserPolicy, CredentialState}, + store::{truthy, AuthorityStore}, +}; +use chrono::Duration; +use hmac::{Hmac, Mac}; +use msg_core::{b64, Error, Json, Result}; +use rand_core::{OsRng, RngCore}; +use sha2::{Digest, Sha256}; +use std::collections::BTreeSet; +use zeroize::Zeroizing; + +/// Adapter implementations use the existing credential/state tables. They must +/// preserve storage's sticky failure, auth-change and writer-fence semantics. +pub trait BrowserStore: AuthorityStore { + fn require_live(&mut self) -> Result<()>; + fn save_credential(&mut self, credential: &Credential, auth_version: u64) -> Result<()>; + fn put_state(&mut self, id: &str, kind: &str, expires: Timestamp, body: &Json) -> Result<()>; + fn replace_state(&mut self, id: &str, body: &Json) -> Result<()>; + fn expire_states(&mut self, now: Timestamp) -> Result<()>; + fn state_count(&self) -> Result; +} + +pub struct Config { + pub enabled: bool, + pub session_ttl: u32, +} + +pub struct BrowserCredential { + pub subject: String, + pub credential_id: String, + pub secret: Zeroizing<[u8; 32]>, +} + +pub struct Service<'a> { + config: Config, + service: &'a str, + token_secret: &'a [u8; 32], + operations: &'a [OperationPolicy], + custodial_ceiling: &'a [Grant], +} + +const CODE_ALPHABET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; +const POST_WRITES: &[&str] = &[ + "content.post_create", + "content.public_board_update", + "content.post_edit", + "discussion.like", + "discussion.unlike", + "discussion.bookmark", + "discussion.unbookmark", + "discussion.reply", + "discussion.fork", + "discussion.prove", + "communication.follow", + "communication.unfollow", +]; + +pub fn state_id(kind: &str, value: &str) -> String { + format!("{kind}:{:x}", Sha256::digest(value.as_bytes())) +} + +fn new_secret() -> Result { + let mut bytes = Zeroizing::new([0u8; 32]); + OsRng + .try_fill_bytes(bytes.as_mut()) + .map_err(|_| Error("randomness_unavailable"))?; + Ok(b64(bytes.as_ref())) +} + +fn get(store: &dyn AuthorityStore, id: &str, now: Timestamp) -> Result { + let state = store.credential_state(id)?.ok_or(Error("invalid_grant"))?; + require(state.expires_at > now, "invalid_grant")?; + Ok(state) +} + +fn field<'a>(value: &'a Json, name: &str) -> Result<&'a Json> { + value.as_object()?.get(name).ok_or(Error("invalid_grant")) +} + +fn string<'a>(value: &'a Json, name: &str) -> Result<&'a str> { + field(value, name)? + .as_str() + .map_err(|_| Error("invalid_grant")) +} + +fn code_id(code: &str) -> Result { + let code = code.to_ascii_uppercase().replace('-', ""); + require( + code.len() == 8 && code.bytes().all(|c| CODE_ALPHABET.contains(&c)), + "invalid_grant", + )?; + Ok(state_id("user", &code)) +} + +fn update(body: Json, values: impl IntoIterator) -> Result { + let mut fields = body.into_object()?; + fields.extend(values.into_iter().map(|(k, v)| (k.to_owned(), v))); + Ok(Json::from_object(fields)) +} + +impl<'a> Service<'a> { + pub fn new( + config: Config, + service: &'a str, + token_secret: &'a [u8; 32], + operations: &'a [OperationPolicy], + custodial_ceiling: &'a [Grant], + ) -> Result { + require( + (60..=86400).contains(&config.session_ttl), + "invalid_browser_config", + )?; + Ok(Self { + config, + service, + token_secret, + operations, + custodial_ceiling, + }) + } + + fn fence(&self, store: &mut S) -> Result<()> { + require(self.config.enabled, "browser_sessions_disabled")?; + store.require_live() + } + + pub fn rate( + &self, + store: &mut S, + address: &str, + now: Timestamp, + ) -> Result<()> { + self.fence(store)?; + store.expire_states(now)?; + let id = state_id( + "rate", + if address.is_empty() { + "unknown" + } else { + address + }, + ); + let state = store.credential_state(&id)?; + let count = state + .as_ref() + .map(|state| decode::(field(&state.body, "count")?)) + .transpose()? + .unwrap_or(0); + require(count < 120, "slow_down")?; + require(store.state_count()? < 100000, "temporarily_unavailable")?; + let expiry = state + .map(|state| state.expires_at) + .unwrap_or(Timestamp(now.0 + Duration::minutes(10))); + store.put_state( + &id, + "rate", + expiry, + &wire(&serde_json::json!({"count": count + 1}))?, + ) + } + + /// Returns a one-time polling secret and a human code, never storing either. + pub fn pending(&self, store: &mut S, now: Timestamp) -> Result { + self.fence(store)?; + for _ in 0..8 { + let mut bytes = [0u8; 8]; + OsRng + .try_fill_bytes(&mut bytes) + .map_err(|_| Error("randomness_unavailable"))?; + let code: String = bytes + .iter() + .map(|b| CODE_ALPHABET[usize::from(b & 31)] as char) + .collect(); + let id = state_id("user", &code); + if store.credential_state(&id)?.is_some() { + continue; + } + let value = new_secret()?; + let expiry = Timestamp(now.0 + Duration::minutes(10)); + let poll = state_id("poll", &value); + let body = wire( + &serde_json::json!({"kind":"browser", "status":"pending", "poll":poll, "interval":5, "next_poll":now}), + )?; + store.put_state(&id, "pending", expiry, &body)?; + store.put_state( + &poll, + "poll", + expiry, + &wire(&serde_json::json!({"user":id}))?, + )?; + return wire(&serde_json::json!({"value": value, "user_code":code, "expires":expiry})); + } + Err(Error("temporarily_unavailable")) + } + + pub fn inspect( + &self, + store: &mut S, + code: &str, + now: Timestamp, + ) -> Result { + self.fence(store)?; + let state = get(store, &code_id(code)?, now)?; + require(string(&state.body, "kind")? == "browser", "invalid_grant")?; + wire( + &serde_json::json!({"kind":"browser", "status":string(&state.body,"status")?, "service":self.service, "purpose":"Approve only the browser sign-in that you started."}), + ) + } + + fn source( + &self, + store: &S, + principal: &Principal, + now: Timestamp, + ) -> Result { + let id = principal + .subject() + .ok_or(Error("identity_owner_required"))?; + require(principal.actor() == Some(id), "identity_owner_required")?; + let subject = store.subject(id)?; + require( + subject.kind == SubjectKind::Registered && !subject.local_only, + "registered_identity_required", + )?; + require(principal.method() == "signature", "signature_required")?; + wire( + &serde_json::json!({"subject":id, "parent":principal.credential_id().ok_or(Error("signature_required"))?, "auth_version":subject.auth_version, "custodial":false, "ceiling":principal.ceiling(), "auth_time":now.0.timestamp()}), + ) + } + + pub fn approve( + &self, + store: &mut S, + principal: &Principal, + code: &str, + decision: &str, + now: Timestamp, + ) -> Result { + self.fence(store)?; + require(matches!(decision, "approve" | "deny"), "invalid_request")?; + let id = code_id(code)?; + let state = get(store, &id, now)?; + require( + string(&state.body, "kind")? == "browser" + && string(&state.body, "status")? == "pending", + "invalid_grant", + )?; + let source = self.source(store, principal, now)?; + let mut body = state.body.into_object()?; + if decision == "approve" { + body.extend(source.into_object()?); + } + let status = if decision == "approve" { + "approved" + } else { + "denied" + }; + body.insert("status".into(), wire(&status)?); + store.replace_state(&id, &Json::from_object(body))?; + wire(&serde_json::json!({"kind":"browser", "status":status})) + } + + /// Pending/slow-down/denied are committed polling outcomes, not transaction + /// failures. This preserves rate state without consuming an approved grant. + pub fn poll( + &self, + store: &mut S, + value: &str, + now: Timestamp, + ) -> Result { + self.fence(store)?; + require(value.len() <= 4096, "invalid_grant")?; + let locator = get(store, &state_id("poll", value), now)?; + let id = string(&locator.body, "user")?; + let state = get(store, id, now)?; + let status = string(&state.body, "status")?.to_owned(); + require( + string(&state.body, "kind")? == "browser" && status != "consumed", + "invalid_grant", + )?; + let mut interval: i64 = decode(field(&state.body, "interval")?)?; + require((1..=86400).contains(&interval), "invalid_grant")?; + let too_soon = Timestamp::parse(string(&state.body, "next_poll")?)? > now; + if too_soon { + interval = interval.checked_add(5).ok_or(Error("invalid_grant"))?; + } + let body = update( + state.body, + [ + ("interval", wire(&interval)?), + ( + "next_poll", + wire(&Timestamp(now.0 + Duration::seconds(interval)))?, + ), + ], + )?; + store.replace_state(id, &body)?; + let error = if too_soon { + Some("slow_down") + } else { + match status.as_str() { + "pending" => Some("authorization_pending"), + "denied" => Some("access_denied"), + "approved" => None, + _ => return Err(Error("invalid_grant")), + } + }; + if let Some(error) = error { + return wire(&serde_json::json!({"error":error})); + } + require_source( + store, + &body, + now, + self.custodial_ceiling, + &mut BTreeSet::new(), + )?; + let body = update(body, [("status", wire(&"consumed")?)])?; + store.replace_state(id, &body)?; + self.create_session(store, &body, None, None, now) + } + + pub fn create_session( + &self, + store: &mut S, + source: &Json, + cookie: Option<&str>, + ttl: Option, + now: Timestamp, + ) -> Result { + self.fence(store)?; + require_source( + store, + source, + now, + self.custodial_ceiling, + &mut BTreeSet::new(), + )?; + require( + ttl.is_none_or(|ttl| (60..=3600).contains(&ttl)), + "invalid_session_ttl", + )?; + let subject = store.subject(string(source, "subject")?)?; + let parent = store.credential(string(source, "parent")?)?; + require( + subject.kind == SubjectKind::Registered + && parent.kind == CredentialKind::SigningKey + && !source + .as_object()? + .get("custodial") + .map(truthy) + .transpose()? + .unwrap_or(false) + && !source + .as_object()? + .get("login_binding") + .map(truthy) + .transpose()? + .unwrap_or(false), + "signature_required", + )?; + let cookie = match cookie { + Some(cookie) if !cookie.is_empty() && cookie.len() <= 4096 => cookie.to_owned(), + Some(_) => return Err(Error("invalid_grant")), + None => new_secret()?, + }; + let ttl = ttl + .unwrap_or(self.config.session_ttl) + .min(self.config.session_ttl); + let mut expiry = Timestamp(now.0 + Duration::seconds(i64::from(ttl))); + if let Some(parent_expiry) = parent.expires_at { + expiry = expiry.min(parent_expiry); + } + let session_id = state_id("session", &cookie); + require( + store.credential_state(&session_id)?.is_none(), + "invalid_grant", + )?; + let credential_id = format!("t_session_{}", random_identifier()?); + let operations: BTreeSet = self + .operations + .iter() + .filter(|op| { + (op.effect == "read" + || (op.version == 1 && POST_WRITES.contains(&op.name.as_str()))) + && !op.require_signature + && !op.anonymous_only + && !["identity.", "root.", "system."] + .iter() + .any(|prefix| op.name.starts_with(prefix)) + }) + .map(|op| format!("{}@{}", op.name, op.version)) + .collect(); + let wiki = BTreeSet::from([ + "content.post_create@1".to_owned(), + "content.post_edit@1".to_owned(), + ]); + let wiki_scope = Scope { + resource_id: "t_wiki".into(), + descendants: true, + }; + let original: Vec = decode(field(source, "ceiling")?)?; + let mut ceiling = Vec::new(); + for grant in original { + let general: BTreeSet = grant + .operations + .intersection(&operations) + .filter(|op| !wiki.contains(*op)) + .cloned() + .collect(); + if !general.is_empty() { + let mut limited = grant.clone(); + limited.operations = general; + ceiling.push(limited); + } + let shared: BTreeSet = grant + .operations + .intersection(&operations) + .filter(|op| wiki.contains(*op)) + .cloned() + .collect(); + if !shared.is_empty() { + let mut limited = grant; + limited.operations = shared; + if scope_subset(&wiki_scope, &limited.scope, store)? { + limited.scope = wiki_scope.clone(); + ceiling.push(limited); + } else if scope_subset(&limited.scope, &wiki_scope, store)? { + ceiling.push(limited); + } + } + } + let credential = Credential { + id: credential_id.clone(), + subject_id: subject.resource_id, + kind: CredentialKind::Token, + verifier: Verifier(Sha256::digest(self.browser_secret(&cookie)?.as_ref()).to_vec()), + ceiling, + not_before: now, + expires_at: Some(expiry), + revoked_at: None, + source_credential_id: Some(parent.id), + }; + store.save_credential(&credential, subject.auth_version)?; + let body = update( + source.clone(), + [("browser_credential", wire(&credential_id)?)], + )?; + store.put_state(&session_id, "session", expiry, &body)?; + store.put_state( + &format!("browser:{credential_id}"), + "browser", + expiry, + &update(body, [("session", wire(&session_id)?)])?, + )?; + wire(&serde_json::json!({"cookie":cookie,"expires":expiry})) + } + + pub fn session( + &self, + store: &mut S, + cookie: &str, + now: Timestamp, + ) -> Result { + self.fence(store)?; + require(!cookie.is_empty() && cookie.len() <= 4096, "invalid_grant")?; + let id = state_id("session", cookie); + let state = get(store, &id, now)?; + require( + !state + .body + .as_object()? + .get("revoked") + .map(truthy) + .transpose()? + .unwrap_or(false), + "invalid_grant", + )?; + require_source( + store, + &state.body, + now, + self.custodial_ceiling, + &mut BTreeSet::new(), + )?; + let mut source = std::collections::BTreeMap::new(); + for key in [ + "subject", + "parent", + "auth_version", + "custodial", + "ceiling", + "auth_time", + ] { + source.insert(key.to_owned(), field(&state.body, key)?.clone()); + } + source.insert("session".into(), wire(&id)?); + wire(&serde_json::json!({"expires":state.expires_at,"source":Json::from_object(source)})) + } + + pub fn browser_secret(&self, cookie: &str) -> Result> { + let mut mac = + Hmac::::new_from_slice(self.token_secret).map_err(|_| Error("invalid_key"))?; + mac.update(b"msg-key-browser-v1:"); + mac.update(cookie.as_bytes()); + Ok(Zeroizing::new(mac.finalize().into_bytes().into())) + } + + pub fn credentials( + &self, + store: &mut S, + cookie: &str, + now: Timestamp, + ) -> Result { + let session = self.session(store, cookie, now)?; + let source = field(&session, "source")?; + let state = get(store, string(source, "session")?, now)?; + let credential = store.credential(string(&state.body, "browser_credential")?)?; + require(credential.current_at(now), "invalid_grant")?; + require_binding( + store, + &credential, + now, + Some(&BrowserPolicy { + enabled: self.config.enabled, + }), + self.custodial_ceiling, + )?; + Ok(BrowserCredential { + subject: string(source, "subject")?.into(), + credential_id: credential.id, + secret: self.browser_secret(cookie)?, + }) + } + + pub fn revoke( + &self, + store: &mut S, + cookie: &str, + now: Timestamp, + ) -> Result<()> { + self.session(store, cookie, now)?; + let id = state_id("session", cookie); + let state = get(store, &id, now)?; + store.replace_state(&id, &update(state.body, [("revoked", wire(&true)?)])?) + } +} + +pub(crate) fn random_identifier() -> Result { + let mut bytes = [0u8; 16]; + OsRng + .try_fill_bytes(&mut bytes) + .map_err(|_| Error("randomness_unavailable"))?; + bytes[6] = (bytes[6] & 15) | 64; + bytes[8] = (bytes[8] & 63) | 128; + Ok(bytes.iter().map(|byte| format!("{byte:02x}")).collect()) +} diff --git a/rust/crates/msg-identity/src/certificates.rs b/rust/crates/msg-identity/src/certificates.rs index 896d4a24..00e8c4e9 100644 --- a/rust/crates/msg-identity/src/certificates.rs +++ b/rust/crates/msg-identity/src/certificates.rs @@ -75,6 +75,30 @@ impl CertificateValidator { } .walk(id, &BTreeSet::new(), None, None) } + /// Validate a proposed native issuance before persistence. The caller must + /// separately authorize issuance and insert its resource in this transaction. + pub fn validate_candidate( + &self, + certificate: &Certificate, + store: &dyn AuthorityStore, + now: Timestamp, + ) -> Result { + Validation { + validator: self, + store, + now, + work: 0, + } + .walk( + &certificate.resource_id, + &BTreeSet::new(), + Some(certificate), + None, + ) + } + pub fn validate_grant(&self, grant: &Grant, store: &dyn AuthorityStore) -> Result<()> { + self.registry.validate_grant(grant, store) + } pub fn validate_publication( &self, cert: &Certificate, diff --git a/rust/crates/msg-identity/src/lib.rs b/rust/crates/msg-identity/src/lib.rs index 481a3907..5b6ea6ff 100644 --- a/rust/crates/msg-identity/src/lib.rs +++ b/rust/crates/msg-identity/src/lib.rs @@ -4,11 +4,15 @@ //! Full resource authorization and the daemon are separate cutover gates. pub mod authentication; pub mod authorization; +pub mod browser; pub mod certificates; +pub mod lifecycle; pub mod models; +pub mod mutations; pub mod policy; pub mod sessions; pub mod store; +pub mod worker_authority; use msg_core::{Error, Result}; diff --git a/rust/crates/msg-identity/src/lifecycle.rs b/rust/crates/msg-identity/src/lifecycle.rs new file mode 100644 index 00000000..b6eeedda --- /dev/null +++ b/rust/crates/msg-identity/src/lifecycle.rs @@ -0,0 +1,1369 @@ +//! Native key identities and response-only token delivery. +//! +//! Authentication, operation-schema validation and resource authorization run +//! before these handlers, in the SAME transaction. These methods never commit. +//! A returned token claim may reach the transport only after its transaction +//! commits; persisted operation results contain identifiers, never token bytes. +use crate::{ + authentication::{OperationPolicy, Principal}, + browser::random_identifier, + certificates::CertificateValidator, + models::*, + mutations::{EncryptionKey, IdentityKey, IdentityStore, TokenDelivery}, + policy::grant_subset, + require, + store::{AuthorityStore, Record}, +}; +use chrono::Duration; +use hmac::{Hmac, Mac}; +use msg_core::{b64, digest, unb64, Error, Json, Result}; +use msg_crypto::{age_recipient, key_id, verify, Ed25519Signer, Signature}; +use sha2::{Digest, Sha256}; +use std::collections::BTreeSet; +use subtle::ConstantTimeEq; +use zeroize::Zeroizing; + +pub struct Service<'a> { + pub namespace_root: &'a str, + pub validator: &'a CertificateValidator, + pub online_signer: &'a Ed25519Signer, + pub token_secret: &'a [u8; 32], + pub primary_ceiling: &'a [Grant], + pub temporary_ceiling: &'a [Grant], + pub base_grants: &'a [Grant], + pub operations: &'a [OperationPolicy], + pub certificate_ttl: u64, + pub temporary_ttl: u64, + pub recovery_window: u64, +} + +struct Issuance<'a> { + subject: &'a str, + key: &'a str, + grants: Option>, + ttl: Option, + authority_source: Option<&'a Certificate>, +} + +fn field<'a>(value: &'a Json, name: &str) -> Result<&'a Json> { + value.as_object()?.get(name).ok_or(Error("invalid_request")) +} +fn text<'a>(value: &'a Json, name: &str) -> Result<&'a str> { + field(value, name)? + .as_str() + .map_err(|_| Error("invalid_request")) +} +fn optional<'a>(value: &'a Json, name: &str) -> Result> { + Ok(value.as_object()?.get(name)) +} +fn after(now: Timestamp, ttl: u64) -> Result { + let seconds = i64::try_from(ttl).map_err(|_| Error("invalid_ttl"))?; + now.0 + .checked_add_signed(Duration::try_seconds(seconds).ok_or(Error("invalid_ttl"))?) + .map(Timestamp) + .ok_or(Error("invalid_ttl")) +} +fn new_id(prefix: &str) -> Result { + Ok(format!("{prefix}_{}", random_identifier()?)) +} +fn output(data: serde_json::Value, resources: Vec) -> Result { + wire(&serde_json::json!({"data":data,"resources":resources})) +} +fn owner(store: &dyn AuthorityStore, principal: &Principal) -> Result { + let id = principal + .subject() + .ok_or(Error("identity_owner_required"))?; + require(principal.actor() == Some(id), "identity_owner_required")?; + let subject = store.subject(id)?; + require(!subject.local_only && id != "u_root", "local_only")?; + Ok(subject) +} +fn signature(principal: &Principal) -> Result<()> { + require(principal.method() == "signature", "signature_required") +} +fn handle(handle: &str) -> Result<()> { + require( + (2..=41).contains(&handle.len()) + && handle.as_bytes()[0].is_ascii_lowercase() + && handle + .bytes() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == b'-') + && !matches!(handle, "root" | "online-ca"), + "invalid_handle", + ) +} +fn bump(subject: &Subject) -> Result { + let mut updated = subject.clone(); + updated.auth_version = subject + .auth_version + .checked_add(1) + .ok_or(Error("invalid_generation"))?; + Ok(updated) +} +fn expected(subject: &Subject) -> Result { + i64::try_from(subject.auth_version).map_err(|_| Error("invalid_generation")) +} +fn token_id(request: &Json, subject: &str) -> Result { + Ok(format!( + "t_{}", + &digest( + wire(&(text(request, "request_id")?, subject))? + .canonical()? + .as_bytes() + )[7..39] + )) +} +fn resource( + id: &str, + kind: &str, + name: &str, + parent: &str, + owner_actor: (&str, &str), + mode: &str, + now: Timestamp, +) -> Result { + let (owner, actor) = owner_actor; + wire( + &serde_json::json!({"id":id,"type":kind,"type_version":1,"name":name, + "parent":parent,"owner":owner,"group":"g_public","mode":mode,"generation":0, + "revision":null,"state":"active","created_at":now,"created_by":actor, + "modified_at":now,"modified_by":actor,"tags":[]}), + ) +} + +impl Service<'_> { + pub fn supports(operation: &str, version: u32) -> bool { + match operation { + "identity.register" | "identity.upgrade" | "identity.token_rotate" => { + (1..=2).contains(&version) + } + "identity.temporary" | "identity.token_create" => (1..=3).contains(&version), + "identity.key_add" + | "identity.key_revoke" + | "identity.token_recover" + | "identity.identity_key_list" + | "identity.identity_key_get" + | "identity.encryption_key_list" + | "identity.encryption_key_get" + | "identity.encryption_key_rotate" + | "identity.certificate_renew" + | "identity.upgrade_result" => version == 1, + _ => false, + } + } + + pub fn handle( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + now: Timestamp, + ) -> Result { + store.require_live()?; + let operation = text(request, "operation")?; + let version: u32 = decode(field(request, "contract_version")?)?; + require(Self::supports(operation, version), "operation_not_found")?; + let arguments = field(request, "arguments")?; + match operation { + "identity.register" => { + require(version == 2, "encryption_subkey_required")?; + self.register(store, principal, request, arguments, now) + } + "identity.temporary" => { + require(version == 3, "temporary_dual_keys_required")?; + self.temporary(store, principal, request, arguments, now) + } + "identity.key_add" => self.key_add(store, principal, request, arguments, now), + "identity.key_revoke" => self.key_revoke(store, principal, arguments, now), + "identity.encryption_key_rotate" => { + self.encryption_rotate(store, principal, arguments, now) + } + "identity.identity_key_list" | "identity.identity_key_get" => { + self.identity_keys(store, arguments, operation.ends_with("_get")) + } + "identity.encryption_key_list" | "identity.encryption_key_get" => { + self.encryption_keys(store, arguments, operation.ends_with("_get")) + } + "identity.token_create" => { + self.token_create(store, principal, request, arguments, version, now) + } + "identity.token_rotate" => { + require(version >= 2, "credential_delivery_upgrade_required")?; + self.token_rotate(store, principal, request, arguments, now) + } + "identity.token_recover" => { + self.token_recover(store, principal, request, arguments, now) + } + "identity.certificate_renew" => self.renew(store, principal, request, arguments, now), + "identity.upgrade" => { + require(version == 2, "encryption_subkey_required")?; + self.upgrade(store, principal, request, arguments, now) + } + "identity.upgrade_result" => self.upgrade_result(store, principal, arguments, now), + _ => Err(Error("operation_not_found")), + } + } + + fn issuer(&self, store: &S, now: Timestamp) -> Result { + let id = store + .setting("online_ca_certificate")? + .ok_or(Error("issuer_not_ready"))?; + let certificate = self.validator.validate(id.as_str()?, store, now)?; + require( + certificate.kind == CertificateKind::Ca + && certificate.key_id == key_id(&self.online_signer.public_key()) + && certificate.issuance.is_some(), + "issuer_not_ready", + )?; + Ok(certificate) + } + + fn issue( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + issuance: Issuance<'_>, + now: Timestamp, + ) -> Result { + let Issuance { + subject, + key, + grants, + ttl, + authority_source, + } = issuance; + let issuer = self.issuer(store, now)?; + let policy = issuer.issuance.as_ref().ok_or(Error("issuer_not_ready"))?; + let lifetime = ttl.unwrap_or(self.certificate_ttl); + require( + lifetime > 0 && lifetime <= policy.max_cert_ttl_seconds, + "certificate_ttl_escalation", + )?; + let mut expiry = after(now, lifetime)?.min(issuer.expires_at); + let grants = match grants { + Some(grants) => grants, + None => { + let mut available = Vec::new(); + let mut seen = BTreeSet::new(); + for requested in self.base_grants { + for allowed in &policy.issue_grants { + if (requested.capability.as_str(), requested.version) + != (allowed.capability.as_str(), allowed.version) + { + continue; + } + let mut candidate = requested.clone(); + candidate.operations = requested + .operations + .intersection(&allowed.operations) + .cloned() + .collect(); + if !candidate.operations.is_empty() + && grant_subset(&candidate, allowed, store)? + { + self.validator.validate_grant(&candidate, store)?; + if seen.insert(wire(&candidate)?.canonical()?) { + available.push(candidate); + } + } + } + } + available + } + }; + if let Some(source) = authority_source { + require( + source.subject_id == subject && source.key_id == key, + "authority_source_invalid", + )?; + expiry = expiry.min(source.expires_at); + } + let mut certificate = Certificate { + resource_id: new_id("cert")?, + serial: new_id("serial")?, + subject_id: subject.into(), + key_id: key.into(), + issuer_id: "u_online_ca".into(), + parent_certificate_id: Some(issuer.resource_id.clone()), + authority_sources: vec![], + kind: CertificateKind::Identity, + grants, + not_before: now, + expires_at: expiry, + target_service: self.validator.service().into(), + delegation_depth: 0, + issuance: None, + signature: Signature { + key_id: key_id(&self.online_signer.public_key()), + algorithm: "ed25519".into(), + value: String::new(), + }, + }; + certificate.signature = self + .online_signer + .sign(&certificate.signing_bytes()?, "certificate")?; + store.insert_identity_resource(&resource( + &certificate.resource_id, + "certificate", + &certificate.resource_id, + "t_certificates", + (subject, "u_online_ca"), + "0444", + now, + )?)?; + self.validator + .validate_candidate(&certificate, store, now)?; + store.register_certificate(&certificate)?; + let source = match authority_source { + Some(source) => { + serde_json::json!({"kind":"certificate","certificate_id":source.resource_id}) + } + None => { + serde_json::json!({"kind":"possession_proof","key_id":key,"request_id":text(request,"request_id")?}) + } + }; + store.append_identity_audit(&wire(&serde_json::json!({ + "event":{"id":new_id("audit")?,"type":"cert.auto.issue","time":now,"request_id":text(request,"request_id")?, + "actor":principal.actor(),"subject":subject,"resources":[{"id":certificate.resource_id}], + "data":{"automatic":true,"policy_version":1,"issuer":"u_online_ca","issuer_certificate_id":issuer.resource_id, + "signing_key_id":key_id(&self.online_signer.public_key()),"certificate_id":certificate.resource_id, + "authority_source":source,"grant_digest":digest(wire(&certificate.grants)?.canonical()?.as_bytes())}}, + "authority":[{"id":authority_source.map(|source| source.resource_id.as_str()).unwrap_or(subject)}], + "before_digest":null,"after_digest":digest(wire(&certificate)?.canonical()?.as_bytes()), + "previous_digest":null,"entry_digest":"","result":"issued" + }))?)?; + Ok(certificate) + } + + fn make_user( + &self, + store: &mut S, + id: &str, + name: &str, + kind: SubjectKind, + now: Timestamp, + ) -> Result { + handle(name)?; + store.claim_handle(self.namespace_root, name, id)?; + store.insert_identity_resource(&resource( + id, + "user", + &format!("@{name}"), + self.namespace_root, + (id, id), + "0755", + now, + )?)?; + let subject = Subject { + resource_id: id.into(), + kind, + primary_group: "g_public".into(), + auth_version: 0, + local_only: false, + }; + store.update_subject(&subject, -1)?; + for (name, mode) in [ + ("keys", "0555"), + ("certificates", "0555"), + ("files", "0700"), + ("keystore", "0700"), + ] { + let folder = format!( + "r_{}", + &digest(wire(&(id, name))?.canonical()?.as_bytes())[7..39] + ); + store.insert_identity_resource(&resource( + &folder, + "topic", + name, + id, + (id, id), + mode, + now, + )?)?; + } + Ok(subject) + } + + fn add_keys( + &self, + store: &mut S, + subject: &Subject, + public: &[u8], + recipient: &str, + ceiling: &[Grant], + now: Timestamp, + ) -> Result<(String, String)> { + require(public.len() == 32, "invalid_public_key")?; + let encryption = age_recipient::public_from_recipient(recipient)?; + require(public != encryption, "encryption_key_must_be_independent")?; + let signing = key_id(public); + let encryption_id = age_recipient::encryption_key_id(&encryption); + let credential = Credential { + id: signing.clone(), + subject_id: subject.resource_id.clone(), + kind: CredentialKind::SigningKey, + verifier: Verifier(public.to_vec()), + ceiling: ceiling.to_vec(), + not_before: now, + expires_at: None, + revoked_at: None, + source_credential_id: None, + }; + store.save_credential(&credential, subject.auth_version)?; + store.save_identity_key(&IdentityKey { + key_id: signing.clone(), + subject: subject.resource_id.clone(), + public_key: Verifier(public.to_vec()), + created_at: now, + retired_at: None, + primary: true, + })?; + store.save_encryption_key(&EncryptionKey { + key_id: encryption_id.clone(), + subject: subject.resource_id.clone(), + recipient: recipient.into(), + public_key: Verifier(encryption.to_vec()), + created_at: now, + retired_at: None, + primary: true, + })?; + Ok((signing, encryption_id)) + } + + fn register( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + args: &Json, + now: Timestamp, + ) -> Result { + signature(principal)?; + self.issuer(store, now)?; + let public = unb64(text(args, "public_key")?, 32)?; + let id = principal.subject().ok_or(Error("subject_mismatch"))?; + require( + id == msg_crypto::subject_id(&public) && principal.actor() == Some(id), + "subject_mismatch", + )?; + let subject = self.make_user( + store, + id, + text(args, "handle")?, + SubjectKind::Registered, + now, + )?; + let (key, encryption) = self.add_keys( + store, + &subject, + &public, + text(args, "encryption_recipient")?, + self.primary_ceiling, + now, + )?; + let certificate = self.issue( + store, + principal, + request, + Issuance { + subject: id, + key: &key, + grants: None, + ttl: None, + authority_source: None, + }, + now, + )?; + output( + serde_json::json!({"subject_id":id,"key_id":key,"encryption_key_id":encryption, + "encryption_recipient":text(args,"encryption_recipient")?,"certificate_id":certificate.resource_id,"handle":text(args,"handle")?}), + vec![ResourceRef { + id: id.into(), + revision: None, + }], + ) + } + + fn temporary( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + args: &Json, + now: Timestamp, + ) -> Result { + let id = principal.subject().ok_or(Error("invalid_bootstrap"))?; + require( + principal.actor() == Some(id) + && principal.method() == "token" + && id.starts_with("u_tmp_"), + "invalid_bootstrap", + )?; + let public = unb64(text(args, "public_key")?, 32)?; + verify(&public,wire(&serde_json::json!({"subject_id":id,"nonce":field(args,"nonce")?,"public_key":field(args,"public_key")?, + "encryption_recipient":field(args,"encryption_recipient")?,"request_id":text(request,"request_id")?}))?.canonical()?.as_bytes(), + &decode(field(args,"possession_proof")?)?,"temporary-key-possession-v1")?; + let subject = self.make_user( + store, + id, + &format!("tmp-{}", &id[id.len() - 24..]), + SubjectKind::Temporary, + now, + )?; + let (signing, encryption) = self.add_keys( + store, + &subject, + &public, + text(args, "encryption_recipient")?, + self.temporary_ceiling, + now, + )?; + let credential = Credential { + id: principal + .credential_id() + .ok_or(Error("invalid_bootstrap"))? + .into(), + subject_id: id.into(), + kind: CredentialKind::Token, + verifier: Verifier(Sha256::digest(self.issued_token(request, id)?.as_ref()).to_vec()), + ceiling: self.temporary_ceiling.to_vec(), + not_before: now, + expires_at: Some(after(now, self.temporary_ttl)?), + revoked_at: None, + source_credential_id: None, + }; + store.save_credential(&credential, 0)?; + self.record_delivery(store, request, &credential, None, now)?; + output( + serde_json::json!({"subject_id":id,"credential_id":credential.id,"expires_at":credential.expires_at, + "identity_key_id":signing,"encryption_key_id":encryption,"encryption_recipient":field(args,"encryption_recipient")?, + "signature_source":"self_custody","server_signable":false,"server_decryptable":false}), + vec![ResourceRef { + id: id.into(), + revision: None, + }], + ) + } + + fn validate_ceiling( + &self, + store: &S, + principal: &Principal, + ceiling: &[Grant], + ) -> Result<()> { + for grant in ceiling { + self.validator.validate_grant(grant, store)?; + let mut allowed = false; + for parent in principal.ceiling() { + allowed |= grant.operations.is_subset(&parent.operations) + && grant_subset(grant, parent, store)?; + } + require(allowed, "credential_ceiling_escalation")?; + } + Ok(()) + } + + fn key_add( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + args: &Json, + now: Timestamp, + ) -> Result { + signature(principal)?; + let subject = owner(store, principal)?; + let public = unb64(text(args, "public_key")?, 32)?; + verify( + &public, + wire(&serde_json::json!({"subject_id":subject.resource_id,"public_key":b64(&public)}))? + .canonical()? + .as_bytes(), + &decode(field(args, "possession_proof")?)?, + "key-add", + )?; + let ceiling: Vec = decode(field(args, "ceiling")?)?; + self.validate_ceiling(store, principal, &ceiling)?; + let key = key_id(&public); + match store.credential(&key) { + Err(Error("credential_not_found")) => (), + Ok(_) => return Err(Error("key_exists")), + Err(error) => return Err(error), + } + let credential = Credential { + id: key.clone(), + subject_id: subject.resource_id.clone(), + kind: CredentialKind::SigningKey, + verifier: Verifier(public), + ceiling: ceiling.clone(), + not_before: now, + expires_at: None, + revoked_at: None, + source_credential_id: None, + }; + store.save_credential(&credential, subject.auth_version)?; + store.save_identity_key(&IdentityKey { + key_id: key.clone(), + subject: subject.resource_id.clone(), + public_key: credential.verifier.clone(), + created_at: now, + retired_at: None, + primary: false, + })?; + store.update_subject(&bump(&subject)?, expected(&subject)?)?; + let issuer = self.issuer(store, now)?; + let policy = issuer.issuance.as_ref().ok_or(Error("issuer_not_ready"))?; + // The CA's signed policy is the concrete online-issuable capability set. + let grants = ceiling + .into_iter() + .filter(|grant| { + policy + .issue_grants + .iter() + .any(|allowed| allowed.capability == grant.capability) + }) + .collect(); + let certificate = self.issue( + store, + principal, + request, + Issuance { + subject: &subject.resource_id, + key: &key, + grants: Some(grants), + ttl: None, + authority_source: None, + }, + now, + )?; + output( + serde_json::json!({"key_id":key,"certificate_id":certificate.resource_id}), + vec![], + ) + } + + fn key_revoke( + &self, + store: &mut S, + principal: &Principal, + args: &Json, + now: Timestamp, + ) -> Result { + signature(principal)?; + let subject = owner(store, principal)?; + let mut credential = store.credential(text(args, "key_id")?)?; + require( + credential.subject_id == subject.resource_id, + "credential_owner_required", + )?; + require( + credential.kind != CredentialKind::SigningKey + || store + .identity_credentials(&subject.resource_id)? + .iter() + .any(|key| { + key.kind == CredentialKind::SigningKey + && key.revoked_at.is_none() + && key.id != credential.id + }), + "last_signing_key", + )?; + credential.revoked_at = Some(now); + store.save_credential(&credential, subject.auth_version)?; + let mut keys = store.identity_keys(&subject.resource_id)?; + keys.sort_by(|a, b| (a.created_at, &a.key_id).cmp(&(b.created_at, &b.key_id))); + let mut replace_primary = false; + for key in &mut keys { + if key.key_id == credential.id { + replace_primary = key.primary; + key.primary = false; + key.retired_at = Some(now); + store.save_identity_key(key)?; + } + } + if replace_primary { + if let Some(key) = keys.iter_mut().find(|key| key.retired_at.is_none()) { + key.primary = true; + store.save_identity_key(key)?; + } + } + store.update_subject(&bump(&subject)?, expected(&subject)?)?; + output( + serde_json::json!({"key_id":credential.id,"revoked":true}), + vec![], + ) + } + + fn encryption_rotate( + &self, + store: &mut S, + principal: &Principal, + args: &Json, + now: Timestamp, + ) -> Result { + signature(principal)?; + let subject = owner(store, principal)?; + require( + !store.custodial_migration_pending(&subject.resource_id, now)?, + "custodial_migration_pending", + )?; + let recipient = text(args, "encryption_recipient")?; + let public = age_recipient::public_from_recipient(recipient)?; + let id = age_recipient::encryption_key_id(&public); + let mut current = store + .encryption_keys(&subject.resource_id)? + .into_iter() + .find(|key| key.primary) + .ok_or(Error("encryption_key_not_found"))?; + require(current.key_id != id, "encryption_key_unchanged")?; + require(!store.encryption_key_exists(&id)?, "encryption_key_exists")?; + current.primary = false; + current.retired_at = Some(now); + store.save_encryption_key(¤t)?; + store.save_encryption_key(&EncryptionKey { + key_id: id.clone(), + subject: subject.resource_id.clone(), + recipient: recipient.into(), + public_key: Verifier(public.to_vec()), + created_at: now, + retired_at: None, + primary: true, + })?; + output( + serde_json::json!({"subject_id":subject.resource_id,"key_id":id,"recipient":recipient,"previous_key_id":current.key_id,"old_ciphertexts_require_rewrap":true}), + vec![], + ) + } + + fn identity_keys(&self, store: &S, args: &Json, get: bool) -> Result { + let subject = store.resolve_identity(text(args, "subject_id")?)?; + store.subject(&subject)?; + let mut keys = store.identity_keys(&subject)?; + keys.sort_by(|a, b| (a.created_at, &a.key_id).cmp(&(b.created_at, &b.key_id))); + let values: Vec<_> = keys.into_iter().map(|key| serde_json::json!({"key_id":key.key_id,"public_key":key.public_key,"created_at":key.created_at,"retired_at":key.retired_at,"primary":key.primary,"algorithm":"ed25519","purpose":"identity"})).collect(); + self.key_output(&subject, args, values, get, "identity_key_not_found") + } + + fn encryption_keys(&self, store: &S, args: &Json, get: bool) -> Result { + let subject = store.resolve_identity(text(args, "subject_id")?)?; + store.subject(&subject)?; + let mut keys = store.encryption_keys(&subject)?; + keys.sort_by(|a, b| (a.created_at, &a.key_id).cmp(&(b.created_at, &b.key_id))); + let values: Vec<_> = keys.into_iter().map(|key| serde_json::json!({"key_id":key.key_id,"recipient":key.recipient,"public_key":key.public_key,"created_at":key.created_at,"retired_at":key.retired_at,"primary":key.primary,"algorithm":"age-x25519","purpose":"encryption"})).collect(); + self.key_output(&subject, args, values, get, "encryption_key_not_found") + } + + fn key_output( + &self, + subject: &str, + args: &Json, + values: Vec, + get: bool, + error: &'static str, + ) -> Result { + if !get { + return output( + serde_json::json!({"subject_id":subject,"keys":values}), + vec![], + ); + } + let requested = optional(args, "key_id")?.map(Json::as_str).transpose()?; + let mut value = values + .into_iter() + .find(|value| match requested { + Some(id) => value["key_id"].as_str() == Some(id), + None => value["primary"].as_bool() == Some(true), + }) + .ok_or(Error(error))?; + value["subject_id"] = subject.into(); + output(value, vec![]) + } + + pub fn issued_token(&self, request: &Json, subject: &str) -> Result> { + let args = field(request, "arguments")?; + let nonce = Zeroizing::new(unb64(text(args, "nonce")?, 64)?); + require(nonce.len() >= 24, "invalid_bootstrap_nonce")?; + let body = wire(&serde_json::json!({"subject":subject,"request_id":text(request,"request_id")?,"nonce":field(args,"nonce")?}))?.canonical()?; + let mut mac = Hmac::::new_from_slice(self.token_secret) + .map_err(|_| Error("invalid_service_key"))?; + mac.update(b"issued-token-v1\0"); + mac.update(body.as_bytes()); + Ok(Zeroizing::new(mac.finalize().into_bytes().into())) + } + + fn record_delivery( + &self, + store: &mut S, + request: &Json, + credential: &Credential, + deadline: Option, + now: Timestamp, + ) -> Result<()> { + let operation = text(request, "operation")?; + let version: u32 = decode(field(request, "contract_version")?)?; + let minimum = match operation { + "identity.temporary" => 3, + "identity.token_create" | "identity.token_rotate" => 2, + "identity.token_recover" => 1, + _ => return Err(Error("credential_delivery_upgrade_required")), + }; + require(version >= minimum, "credential_delivery_upgrade_required")?; + let args = field(request, "arguments")?; + let field_name = if operation == "identity.token_recover" { + "new_recovery_secret" + } else { + "recovery_secret" + }; + let secret = Zeroizing::new(unb64(text(args, field_name)?, 64)?); + require(secret.len() >= 32, "invalid_recovery_secret")?; + require( + *secret != *Zeroizing::new(unb64(text(args, "nonce")?, 64)?), + "recovery_secret_not_independent", + )?; + if operation == "identity.token_recover" { + require( + *secret != *Zeroizing::new(unb64(text(args, "recovery_secret")?, 64)?), + "recovery_secret_not_independent", + )?; + } + let expires = credential + .expires_at + .ok_or(Error("recovery_unavailable"))? + .min(match deadline { + Some(deadline) => deadline, + None => after(now, self.recovery_window)?, + }); + require(expires > now, "recovery_unavailable")?; + let mut hash = Sha256::new(); + hash.update(b"token-recovery-v1\0"); + hash.update(secret.as_slice()); + store.insert_token_delivery(&TokenDelivery { + credential_id: credential.id.clone(), + subject: credential.subject_id.clone(), + request_id: text(request, "request_id")?.into(), + request_digest: text(request, "payload_digest")?.into(), + recovery_verifier: format!("{:x}", hash.finalize()), + recovery_expires_at: expires, + claimed_at: None, + consumed_at: None, + }) + } + + fn token_create( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + args: &Json, + version: u32, + now: Timestamp, + ) -> Result { + signature(principal)?; + require(version >= 2, "credential_delivery_upgrade_required")?; + let subject = owner(store, principal)?; + let ceiling = if version == 3 && optional(args, "ceiling")?.is_none() { + let reads: BTreeSet = self + .operations + .iter() + .filter(|op| { + op.effect == "read" + && !op.require_signature + && !["identity.", "root.", "system."] + .iter() + .any(|prefix| op.name.starts_with(prefix)) + }) + .map(|op| format!("{}@{}", op.name, op.version)) + .collect(); + principal + .ceiling() + .iter() + .filter_map(|grant| { + let mut grant = grant.clone(); + grant.operations = grant.operations.intersection(&reads).cloned().collect(); + (!grant.operations.is_empty()).then_some(grant) + }) + .collect() + } else { + decode(field(args, "ceiling")?)? + }; + self.validate_ceiling(store, principal, &ceiling)?; + let ttl: u64 = decode(field(args, "ttl")?)?; + require((1..=86400).contains(&ttl), "invalid_ttl")?; + let credential = Credential { + id: token_id(request, &subject.resource_id)?, + subject_id: subject.resource_id.clone(), + kind: CredentialKind::Token, + verifier: Verifier( + Sha256::digest(self.issued_token(request, &subject.resource_id)?.as_ref()).to_vec(), + ), + ceiling, + not_before: now, + expires_at: Some(after(now, ttl)?), + revoked_at: None, + source_credential_id: if version >= 3 { + Some( + principal + .credential_id() + .ok_or(Error("signature_required"))? + .into(), + ) + } else { + None + }, + }; + store.save_credential(&credential, subject.auth_version)?; + self.record_delivery(store, request, &credential, None, now)?; + if version >= 3 { + store.put_state(&format!("api:{}",credential.id),"api",credential.expires_at.ok_or(Error("invalid_ttl"))?,&wire(&serde_json::json!({"subject":subject.resource_id,"parent":principal.credential_id(),"auth_version":subject.auth_version}))?)?; + if let Some(previous) = optional(args, "previous_credential")? { + let mut old = store.credential(previous.as_str()?)?; + require( + old.kind == CredentialKind::Token + && old.subject_id == subject.resource_id + && old.revoked_at.is_none(), + "credential_owner_required", + )?; + require( + store + .credential_state(&format!("api:{}", old.id))? + .is_some(), + "api_key_required", + )?; + old.revoked_at = Some(now); + store.save_credential(&old, subject.auth_version)?; + } + } + output( + serde_json::json!({"subject_id":subject.resource_id,"credential_id":credential.id,"expires_at":credential.expires_at}), + vec![], + ) + } + + fn token_rotate( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + _args: &Json, + now: Timestamp, + ) -> Result { + let subject = owner(store, principal)?; + let mut old = + store.credential(principal.credential_id().ok_or(Error("token_required"))?)?; + require(old.kind == CredentialKind::Token, "token_required")?; + // Derived API/browser grants must stay bound to their parent. Rotation + // cannot turn a limited source-bound credential into an independent one. + let credential = Credential { + id: token_id(request, &subject.resource_id)?, + subject_id: subject.resource_id.clone(), + kind: CredentialKind::Token, + verifier: Verifier( + Sha256::digest(self.issued_token(request, &subject.resource_id)?.as_ref()).to_vec(), + ), + ceiling: old.ceiling.clone(), + not_before: now, + expires_at: Some(after(now, self.temporary_ttl)?), + revoked_at: None, + source_credential_id: old.source_credential_id.clone(), + }; + require(!old.id.starts_with("t_session_"), "signature_required")?; + store.save_credential(&credential, subject.auth_version)?; + self.record_delivery(store, request, &credential, None, now)?; + if let Some(api) = store.credential_state(&format!("api:{}", old.id))? { + store.put_state( + &format!("api:{}", credential.id), + "api", + credential.expires_at.ok_or(Error("invalid_ttl"))?, + &api.body, + )?; + } + old.revoked_at = Some(now); + store.save_credential(&old, subject.auth_version)?; + output( + serde_json::json!({"subject_id":subject.resource_id,"credential_id":credential.id,"previous_credential":old.id,"expires_at":credential.expires_at}), + vec![], + ) + } + + fn token_recover( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + args: &Json, + now: Timestamp, + ) -> Result { + require(principal.method() == "recovery", "invalid_recovery_proof")?; + let subject = owner(store, principal)?; + let mut old = store.credential(text(args, "credential_id")?)?; + require( + old.subject_id == subject.resource_id + && old.kind == CredentialKind::Token + && old.current_at(now), + "recovery_unavailable", + )?; + let delivery = store + .token_delivery(&old.id)? + .ok_or(Error("recovery_unavailable"))?; + require( + delivery.subject == subject.resource_id + && delivery.request_id == text(args, "original_request_id")? + && delivery.consumed_at.is_none() + && delivery.recovery_expires_at > now + && text(request, "request_id")? != delivery.request_id, + "recovery_unavailable", + )?; + let credential = Credential { + id: token_id(request, &subject.resource_id)?, + subject_id: subject.resource_id.clone(), + kind: CredentialKind::Token, + verifier: Verifier( + Sha256::digest(self.issued_token(request, &subject.resource_id)?.as_ref()).to_vec(), + ), + ceiling: old.ceiling.clone(), + not_before: now, + expires_at: old.expires_at, + revoked_at: None, + source_credential_id: old.source_credential_id.clone(), + }; + require(credential.id != old.id, "recovery_unavailable")?; + self.record_delivery( + store, + request, + &credential, + Some(delivery.recovery_expires_at), + now, + )?; + store.save_credential(&credential, subject.auth_version)?; + old.revoked_at = Some(now); + store.save_credential(&old, subject.auth_version)?; + if let Some(api) = store.credential_state(&format!("api:{}", old.id))? { + store.put_state( + &format!("api:{}", credential.id), + "api", + credential.expires_at.ok_or(Error("recovery_unavailable"))?, + &api.body, + )?; + } + require( + store.consume_token_delivery(&old.id, now)?, + "recovery_unavailable", + )?; + output( + serde_json::json!({"subject_id":subject.resource_id,"credential_id":credential.id,"previous_credential":old.id,"expires_at":credential.expires_at}), + vec![], + ) + } + + /// Call in a fresh write transaction AFTER the result transaction committed; + /// release the returned secret only AFTER this claim transaction commits. + pub fn claim_delivery( + &self, + store: &mut S, + request: &Json, + subject: &str, + credential_id: &str, + now: Timestamp, + ) -> Result> { + store.require_live()?; + let credential = store.credential(credential_id)?; + require( + credential.subject_id == subject + && credential.kind == CredentialKind::Token + && credential.current_at(now), + "credential_expired", + )?; + let token = self.issued_token(request, subject)?; + require( + bool::from( + credential + .verifier + .0 + .as_slice() + .ct_eq(Sha256::digest(token.as_ref()).as_slice()), + ), + "invalid_token_result", + )?; + let delivery = store + .token_delivery(credential_id)? + .ok_or(Error("token_delivery_unavailable"))?; + require( + delivery.subject == subject + && delivery.request_id == text(request, "request_id")? + && delivery.request_digest == text(request, "payload_digest")? + && delivery.claimed_at.is_none() + && delivery.consumed_at.is_none(), + "token_delivery_unavailable", + )?; + require( + store.claim_token_delivery(credential_id, now)?, + "token_delivery_unavailable", + )?; + Ok(token) + } + + fn renew( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + args: &Json, + now: Timestamp, + ) -> Result { + signature(principal)?; + let subject = owner(store, principal)?; + require( + subject.kind == SubjectKind::Registered, + "registered_identity_required", + )?; + self.issuer(store, now)?; + let mut candidates = Vec::new(); + for certificate in store.identity_certificates(&subject.resource_id)? { + if certificate.kind != CertificateKind::Identity + || Some(certificate.key_id.as_str()) != principal.credential_id() + { + continue; + } + match self + .validator + .validate(&certificate.resource_id, store, now) + { + Ok(current) => candidates.push(current), + Err(Error( + "certificate_revoked" + | "certificate_expired" + | "certificate_key_revoked" + | "credential_revoked" + | "authority_source_inactive" + | "authority_source_changed" + | "authority_source_expired" + | "authority_source_lost", + )) => (), + Err(error) => return Err(error), + } + } + candidates + .sort_by(|a, b| (a.not_before, &a.resource_id).cmp(&(b.not_before, &b.resource_id))); + let source = candidates.first().ok_or(Error("renewal_source_required"))?; + let remaining = u64::try_from((source.expires_at.0 - now.0).num_seconds()) + .map_err(|_| Error("renewal_source_expiring"))?; + require(remaining >= 1, "renewal_source_expiring")?; + let ttl = optional(args, "ttl")? + .map(decode::) + .transpose()? + .unwrap_or(remaining); + require(ttl <= remaining, "renewal_ttl_exceeded")?; + let grants = optional(args, "grants")? + .map(decode::>) + .transpose()? + .unwrap_or_else(|| source.grants.clone()); + for grant in &grants { + self.validator.validate_grant(grant, store)?; + let mut allowed = false; + for parent in &source.grants { + allowed |= grant.operations.is_subset(&parent.operations) + && grant_subset(grant, parent, store)?; + } + require(allowed, "renewal_scope_exceeded")?; + } + let cert = self.issue( + store, + principal, + request, + Issuance { + subject: &subject.resource_id, + key: &source.key_id, + grants: Some(grants), + ttl: Some(ttl), + authority_source: Some(source), + }, + now, + )?; + output( + serde_json::json!({"subject_id":subject.resource_id,"certificate_id":cert.resource_id,"expires_at":cert.expires_at}), + vec![], + ) + } + + fn upgrade( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + args: &Json, + now: Timestamp, + ) -> Result { + self.issuer(store, now)?; + let subject = owner(store, principal)?; + require(subject.kind == SubjectKind::Temporary, "not_temporary")?; + let public = unb64(text(args, "public_key")?, 32)?; + let encryption = age_recipient::public_from_recipient(text(args, "encryption_recipient")?)?; + require(public != encryption, "encryption_key_must_be_independent")?; + verify(&public,wire(&serde_json::json!({"subject_id":subject.resource_id,"public_key":field(args,"public_key")?,"handle":field(args,"handle")?,"encryption_recipient":field(args,"encryption_recipient")?}))?.canonical()?.as_bytes(),&decode(field(args,"possession_proof")?)?,"upgrade")?; + let name = text(args, "handle")?; + handle(name)?; + let mut record = store + .record(Record::Resource, &subject.resource_id)? + .into_object()?; + let parent = record + .get("parent") + .ok_or(Error("not_found"))? + .as_str()? + .to_owned(); + let generation: i64 = decode(record.get("generation").ok_or(Error("not_found"))?)?; + store.claim_handle(&parent, name, &subject.resource_id)?; + record.insert("name".into(), wire(&format!("@{name}"))?); + record.insert( + "generation".into(), + wire( + &generation + .checked_add(1) + .ok_or(Error("invalid_generation"))?, + )?, + ); + record.insert("modified_at".into(), wire(&now)?); + record.insert("modified_by".into(), wire(&principal.actor())?); + store.replace_identity_resource(&Json::from_object(record), generation)?; + let mut updated = bump(&subject)?; + updated.kind = SubjectKind::Registered; + store.update_subject(&updated, expected(&subject)?)?; + let key = key_id(&public); + let encryption_id = age_recipient::encryption_key_id(&encryption); + let identity_keys = store.identity_keys(&subject.resource_id)?; + if let Some(existing) = identity_keys.iter().find(|key| key.primary) { + require( + existing.key_id == key && existing.public_key.0 == public, + "temporary_identity_key_mismatch", + )?; + } else { + store.save_identity_key(&IdentityKey { + key_id: key.clone(), + subject: subject.resource_id.clone(), + public_key: Verifier(public.clone()), + created_at: now, + retired_at: None, + primary: true, + })?; + } + let encryption_keys = store.encryption_keys(&subject.resource_id)?; + if let Some(existing) = encryption_keys.iter().find(|key| key.primary) { + require( + existing.key_id == encryption_id + && existing.recipient == text(args, "encryption_recipient")?, + "temporary_encryption_key_mismatch", + )?; + } else { + store.save_encryption_key(&EncryptionKey { + key_id: encryption_id.clone(), + subject: subject.resource_id.clone(), + recipient: text(args, "encryption_recipient")?.into(), + public_key: Verifier(encryption.to_vec()), + created_at: now, + retired_at: None, + primary: true, + })?; + } + let credential = Credential { + id: key.clone(), + subject_id: subject.resource_id.clone(), + kind: CredentialKind::SigningKey, + verifier: Verifier(public), + ceiling: self.primary_ceiling.to_vec(), + not_before: now, + expires_at: None, + revoked_at: None, + source_credential_id: None, + }; + store.save_credential(&credential, updated.auth_version)?; + let certificate = self.issue( + store, + principal, + request, + Issuance { + subject: &subject.resource_id, + key: &key, + grants: None, + ttl: None, + authority_source: None, + }, + now, + )?; + for mut old in store.identity_credentials(&subject.resource_id)? { + if old.kind == CredentialKind::Token && old.revoked_at.is_none() { + old.revoked_at = Some(now); + store.save_credential(&old, updated.auth_version)?; + } + } + output( + serde_json::json!({"subject_id":subject.resource_id,"key_id":key,"encryption_key_id":encryption_id,"encryption_recipient":field(args,"encryption_recipient")?,"certificate_id":certificate.resource_id,"handle":name}), + vec![ResourceRef { + id: subject.resource_id, + revision: None, + }], + ) + } + + fn upgrade_result( + &self, + store: &S, + principal: &Principal, + args: &Json, + now: Timestamp, + ) -> Result { + let subject = owner(store, principal)?; + require( + subject.kind == SubjectKind::Registered && principal.method() == "signature", + "self_custody_signature_required", + )?; + let result = store + .identity_result(&subject.resource_id, text(args, "upgrade_request_id")?)? + .ok_or(Error("upgrade_not_completed"))?; + require( + text(&result, "operation")? == "identity.upgrade" + && text(&result, "status")? == "ok" + && text(&result, "subject")? == subject.resource_id, + "upgrade_not_completed", + )?; + let data = field(&result, "data")?; + require( + Some(text(data, "key_id")?) == principal.credential_id(), + "upgrade_new_key_required", + )?; + require( + store + .identity_keys(&subject.resource_id)? + .iter() + .any(|key| { + key.primary + && key.retired_at.is_none() + && Some(key.key_id.as_str()) == principal.credential_id() + }), + "upgrade_new_key_required", + )?; + require( + store + .encryption_keys(&subject.resource_id)? + .iter() + .any(|key| { + key.key_id == text(data, "encryption_key_id").unwrap_or("") + && key.recipient == text(data, "encryption_recipient").unwrap_or("") + && key.retired_at.is_none() + }), + "upgrade_encryption_key_changed", + )?; + let certificate = self + .validator + .validate(text(data, "certificate_id")?, store, now)?; + require( + certificate.subject_id == subject.resource_id + && certificate.key_id == text(data, "key_id")?, + "upgrade_certificate_mismatch", + )?; + output( + serde_json::json!({"subject_id":field(data,"subject_id")?,"key_id":field(data,"key_id")?,"encryption_key_id":field(data,"encryption_key_id")?,"encryption_recipient":field(data,"encryption_recipient")?,"certificate_id":field(data,"certificate_id")?,"handle":field(data,"handle")?,"status":"completed","upgrade_request_id":field(args,"upgrade_request_id")?,"upgrade_committed_at":field(&result,"committed_at")?}), + vec![], + ) + } +} diff --git a/rust/crates/msg-identity/src/mutations.rs b/rust/crates/msg-identity/src/mutations.rs new file mode 100644 index 00000000..e5a23ac9 --- /dev/null +++ b/rust/crates/msg-identity/src/mutations.rs @@ -0,0 +1,70 @@ +//! Typed ports for identity mutations inside one existing metadata transaction. +//! There is no arbitrary SQL escape hatch and no commit operation on this port. +use crate::{browser::BrowserStore, models::*}; +use msg_core::{Json, Result}; + +#[derive(Clone)] +pub struct IdentityKey { + pub key_id: String, + pub subject: String, + pub public_key: Verifier, + pub created_at: Timestamp, + pub retired_at: Option, + pub primary: bool, +} + +#[derive(Clone)] +pub struct EncryptionKey { + pub key_id: String, + pub subject: String, + pub recipient: String, + pub public_key: Verifier, + pub created_at: Timestamp, + pub retired_at: Option, + pub primary: bool, +} + +#[derive(Clone)] +pub struct TokenDelivery { + pub credential_id: String, + pub subject: String, + pub request_id: String, + pub request_digest: String, + pub recovery_verifier: String, + pub recovery_expires_at: Timestamp, + pub claimed_at: Option, + pub consumed_at: Option, +} + +pub trait IdentityStore: BrowserStore { + /// Resolve the caller's reference without changing the signed request. + fn resolve_identity(&self, reference: &str) -> Result; + /// Compare the persisted auth version; -1 permits only a new identity. + fn update_subject(&mut self, subject: &Subject, expected: i64) -> Result<()>; + /// Check live names AND retained aliases under the namespace root. + fn claim_handle(&mut self, parent: &str, handle: &str, subject: &str) -> Result<()>; + /// Decode and use the metadata store's typed resource insertion API. + fn insert_identity_resource(&mut self, resource: &Json) -> Result<()>; + fn replace_identity_resource(&mut self, resource: &Json, expected: i64) -> Result<()>; + fn set_identity_setting(&mut self, key: &str, value: &Json) -> Result<()>; + fn identity_result(&self, subject: &str, request: &str) -> Result>; + fn identity_credentials(&self, subject: &str) -> Result>; + fn identity_keys(&self, subject: &str) -> Result>; + /// Only retirement/primary fields may change for an existing key row. + fn save_identity_key(&mut self, key: &IdentityKey) -> Result<()>; + fn encryption_keys(&self, subject: &str) -> Result>; + fn encryption_key_exists(&self, key: &str) -> Result; + /// Only retirement/primary fields may change for an existing key row. + fn save_encryption_key(&mut self, key: &EncryptionKey) -> Result<()>; + fn custodial_migration_pending(&self, subject: &str, now: Timestamp) -> Result; + fn identity_certificates(&self, subject: &str) -> Result>; + fn register_certificate(&mut self, certificate: &Certificate) -> Result<()>; + /// Decode and append through the store's hash-chained audit API. + fn append_identity_audit(&mut self, audit: &Json) -> Result<()>; + fn insert_token_delivery(&mut self, delivery: &TokenDelivery) -> Result<()>; + fn token_delivery(&self, credential: &str) -> Result>; + /// Atomically require claimed_at AND consumed_at NULL, then set claimed_at. + fn claim_token_delivery(&mut self, credential: &str, now: Timestamp) -> Result; + /// Atomically require consumed_at NULL, then set consumed_at. + fn consume_token_delivery(&mut self, credential: &str, now: Timestamp) -> Result; +} diff --git a/rust/crates/msg-identity/src/sessions.rs b/rust/crates/msg-identity/src/sessions.rs index 945ce9a1..14c4a619 100644 --- a/rust/crates/msg-identity/src/sessions.rs +++ b/rust/crates/msg-identity/src/sessions.rs @@ -52,7 +52,11 @@ fn get(store: &dyn AuthorityStore, id: &str, now: Timestamp) -> Result { require(state.expires_at > now, "invalid_grant")?; Ok(state.body) } -fn require_ceiling(store: &dyn AuthorityStore, ceiling: &[Grant], parent: &[Grant]) -> Result<()> { +pub(crate) fn require_ceiling( + store: &dyn AuthorityStore, + ceiling: &[Grant], + parent: &[Grant], +) -> Result<()> { for grant in ceiling { let mut allowed = false; for source in parent { @@ -62,7 +66,7 @@ fn require_ceiling(store: &dyn AuthorityStore, ceiling: &[Grant], parent: &[Gran } Ok(()) } -fn require_source( +pub(crate) fn require_source( store: &dyn AuthorityStore, body: &Json, now: Timestamp, diff --git a/rust/crates/msg-identity/src/worker_authority.rs b/rust/crates/msg-identity/src/worker_authority.rs new file mode 100644 index 00000000..0b0af760 --- /dev/null +++ b/rust/crates/msg-identity/src/worker_authority.rs @@ -0,0 +1,222 @@ +//! A queued snapshot is evidence of prior admission, not a fresh principal. +//! Only a live, persisted attempt may be revalidated through this store port. +use crate::{ + authentication::Principal, + authorization::AuthorizationService, + models::{decode, CredentialKind, Grant, Timestamp}, + policy::scope_subset, + require, + sessions::{require_binding, BrowserPolicy}, + store::{setting_truthy, AuthorityStore}, +}; +use msg_core::{Error, Json, Result}; +use serde::Deserialize; +use std::collections::BTreeMap; + +pub trait WorkerAuthorityStore: AuthorityStore { + fn authority(&self) -> &dyn AuthorityStore; + /// Return the persisted snapshot only when the same attempt is running and + /// its lease is live. Implementations must read in this authority snapshot. + fn worker_snapshot(&self, id: &str, attempt: i64, now: Timestamp) -> Result; +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +struct Snapshot { + actor: String, + subject: String, + credential_id: String, + method: String, + certificates: Vec, + ceiling: Vec, +} + +pub struct WorkerAuthority { + browser_policy: Option, + custodial_ceiling: Vec, +} +impl WorkerAuthority { + /// Use the service's temporary (custodial) ceiling, not its primary ceiling. + pub fn new(browser_policy: Option, custodial_ceiling: Vec) -> Self { + Self { + browser_policy, + custodial_ceiling, + } + } + + pub fn current( + &self, + id: &str, + attempt: i64, + authorizer: &mut AuthorizationService, + store: &dyn WorkerAuthorityStore, + now: Timestamp, + ) -> Result { + let original: Snapshot = decode(&store.worker_snapshot(id, attempt, now)?) + .map_err(|_| Error("worker_identity_required"))?; + let (method, kind) = match original.method.as_str() { + "signature" => ("signature", CredentialKind::SigningKey), + "token" => ("token", CredentialKind::Token), + "ssh" => ("ssh", CredentialKind::SshKey), + _ => return Err(Error("worker_identity_required")), + }; + require( + original.actor != "u_root" && original.subject != "u_root", + "local_only", + )?; + let credential = store.credential(&original.credential_id)?; + require( + credential.subject_id == original.actor, + "credential_subject_mismatch", + )?; + require(credential.kind == kind, "credential_kind_mismatch")?; + require(credential.revoked_at.is_none(), "credential_revoked")?; + require(credential.current_at(now), "credential_expired")?; + for subject in [&original.actor, &original.subject] { + require(!store.subject(subject)?.local_only, "local_only")?; + require( + !setting_truthy(store.authority(), &format!("identity_archived:{subject}"))?, + "account_archived", + )?; + } + require_binding( + store.authority(), + &credential, + now, + self.browser_policy.as_ref(), + &self.custodial_ceiling, + )?; + let mut ceiling = Vec::new(); + for captured in &original.ceiling { + for live in &credential.ceiling { + if captured.capability != live.capability || captured.version != live.version { + continue; + } + let operations = captured + .operations + .intersection(&live.operations) + .cloned() + .collect::>(); + if operations.is_empty() { + continue; + } + let scope = if scope_subset(&captured.scope, &live.scope, store.authority())? { + captured.scope.clone() + } else if scope_subset(&live.scope, &captured.scope, store.authority())? { + live.scope.clone() + } else { + continue; + }; + if let Some(constraints) = + intersect_constraints(&captured.constraints, &live.constraints)? + { + ceiling.push(Grant { + capability: captured.capability.clone(), + version: captured.version, + scope, + operations, + constraints, + }); + } + } + } + require(!ceiling.is_empty(), "credential_ceiling")?; + let principal = Principal::from_worker( + original.actor, + original.subject, + original.credential_id, + method, + original.certificates, + ceiling, + ); + // Live certificate revocation, issuer authority and source revisions are + // checked after narrowing, in the very same database snapshot. + authorizer.grants(&principal, store.authority(), now)?; + Ok(principal) + } +} + +fn intersect_constraints( + captured: &BTreeMap, + live: &BTreeMap, +) -> Result>> { + let mut constraints = captured.clone(); + for (name, value) in live { + let Some(previous) = constraints.get(name) else { + constraints.insert(name.clone(), value.clone()); + continue; + }; + let narrowed = match name.as_str() { + "hosts" | "methods" | "ports" | "schemes" => { + let previous = previous.as_array()?; + let mut values = value + .as_array()? + .iter() + .filter(|v| previous.contains(v)) + .cloned() + .collect::>(); + if values.is_empty() { + return Ok(None); + } + values.sort_by_key(|v| v.canonical().unwrap_or_default()); + values.dedup(); + Json::parse( + &serde_json::to_string(&values) + .map_err(|_| Error("invalid_identity_record"))?, + )? + } + "timeout_ms" | "max_response_bytes" | "max_redirects" => { + // Network limits have an integer schema. Never narrow through + // a floating point conversion that could enlarge the authority. + let a: u64 = previous + .as_integer()? + .parse() + .map_err(|_| Error("invalid_network_limit"))?; + let b: u64 = value + .as_integer()? + .parse() + .map_err(|_| Error("invalid_network_limit"))?; + Json::parse(&a.min(b).to_string())? + } + _ if previous == value => previous.clone(), + _ => return Ok(None), + }; + constraints.insert(name.clone(), narrowed); + } + Ok(Some(constraints)) +} + +#[cfg(test)] +mod tests { + use super::*; + fn constraints(raw: &str) -> BTreeMap { + Json::parse(raw).unwrap().into_object().unwrap() + } + + #[test] + fn queued_authority_never_grows_with_live_credential() { + let old = constraints( + r#"{"hosts":["a.example","b.example"],"max_response_bytes":100,"custom":"x"}"#, + ); + let live = constraints( + r#"{"hosts":["b.example","c.example"],"max_response_bytes":500,"methods":["GET"]}"#, + ); + let narrowed = intersect_constraints(&old, &live).unwrap().unwrap(); + assert!( + narrowed + == constraints( + r#"{"hosts":["b.example"],"max_response_bytes":100,"custom":"x","methods":["GET"]}"# + ) + ); + assert!( + intersect_constraints(&old, &constraints(r#"{"hosts":["c.example"]}"#)) + .unwrap() + .is_none() + ); + assert!( + intersect_constraints(&old, &constraints(r#"{"custom":"y"}"#)) + .unwrap() + .is_none() + ); + } +} diff --git a/rust/crates/msg-identity/tests/browser_lifecycle.rs b/rust/crates/msg-identity/tests/browser_lifecycle.rs new file mode 100644 index 00000000..aa0ee999 --- /dev/null +++ b/rust/crates/msg-identity/tests/browser_lifecycle.rs @@ -0,0 +1,417 @@ +use msg_core::{b64, Error, Json, Result}; +use msg_crypto::{key_id, subject_id, Ed25519Signer, Signature}; +use msg_identity::{ + authentication::{Admission, AuthenticationService, Entry, OperationPolicy, Principal}, + browser::{state_id, BrowserStore, Config, Service}, + certificates::CertificateValidator, + models::*, + policy::Registry, + sessions::CredentialState, + store::{AuthorityStore, Record, RecoveryDelivery}, +}; +use msg_protocol::Request; +use std::collections::{BTreeMap, BTreeSet}; + +#[derive(Default)] +struct Memory { + records: BTreeMap<(u8, String), Json>, + states: BTreeMap, + blocked: bool, +} +fn kind(record: Record) -> u8 { + match record { + Record::Subject => 0, + Record::Credential => 1, + Record::Certificate => 2, + Record::Resource => 3, + } +} +fn now() -> Timestamp { + Timestamp::parse("2026-10-22T00:00:00Z").unwrap() +} +fn later(seconds: i64) -> Timestamp { + Timestamp(now().0 + chrono::Duration::seconds(seconds)) +} +impl AuthorityStore for Memory { + fn record(&self, record: Record, id: &str) -> Result { + self.records + .get(&(kind(record), id.into())) + .cloned() + .ok_or(Error("not_found")) + } + fn setting(&self, _: &str) -> Result> { + Ok(None) + } + fn has_setting(&self, _: &str) -> Result { + Ok(false) + } + fn certificate_revoked(&self, _: &str) -> Result { + Ok(false) + } + fn request_result(&self, _: Option<&str>, _: &str, _: &str) -> Result> { + Ok(None) + } + fn credential_state(&self, id: &str) -> Result> { + Ok(self.states.get(id).cloned()) + } + fn custodial_binding(&self, _: &str) -> Result> { + Ok(None) + } + fn recovery_delivery(&self, _: &str, _: &str) -> Result> { + Ok(None) + } +} +impl BrowserStore for Memory { + fn require_live(&mut self) -> Result<()> { + if self.blocked { + Err(Error("recovery_quarantined")) + } else { + Ok(()) + } + } + fn save_credential(&mut self, credential: &Credential, version: u64) -> Result<()> { + if self.subject(&credential.subject_id)?.auth_version != version { + return Err(Error("auth_version_conflict")); + } + self.records + .insert((1, credential.id.clone()), wire(credential)?); + Ok(()) + } + fn put_state(&mut self, id: &str, _: &str, expires: Timestamp, body: &Json) -> Result<()> { + self.states.insert( + id.into(), + CredentialState { + expires_at: expires, + body: body.clone(), + }, + ); + Ok(()) + } + fn replace_state(&mut self, id: &str, body: &Json) -> Result<()> { + self.states.get_mut(id).ok_or(Error("invalid_grant"))?.body = body.clone(); + Ok(()) + } + fn expire_states(&mut self, now: Timestamp) -> Result<()> { + self.states.retain(|_, state| state.expires_at > now); + Ok(()) + } + fn state_count(&self) -> Result { + Ok(self.states.len() as u64) + } +} +fn root(signer: &Ed25519Signer) -> Certificate { + let mut certificate = Certificate { + resource_id: "cert_root".into(), + serial: "serial_root".into(), + subject_id: "u_root".into(), + key_id: key_id(&signer.public_key()), + issuer_id: "u_root".into(), + parent_certificate_id: None, + authority_sources: vec![], + kind: CertificateKind::Ca, + grants: vec![], + not_before: now(), + expires_at: later(86400), + target_service: "https://fixture.test".into(), + delegation_depth: 0, + issuance: Some(IssuancePolicy { + issue_grants: vec![], + max_cert_ttl_seconds: 86400, + max_child_ca_depth: 1, + max_delegation_depth: 0, + }), + signature: Signature { + key_id: String::new(), + algorithm: "ed25519".into(), + value: String::new(), + }, + }; + certificate.signature = signer + .sign(&certificate.signing_bytes().unwrap(), "certificate") + .unwrap(); + certificate +} +fn operation(name: &str, effect: &str, signature: bool) -> OperationPolicy { + OperationPolicy { + name: name.into(), + version: 1, + effect: effect.into(), + entries: BTreeSet::from(["network".into()]), + require_signature: signature, + anonymous_only: false, + } +} +fn operations() -> Vec { + vec![ + operation("identity.session_approve", "write", true), + operation("content.post_get", "read", false), + operation("content.post_create", "write", false), + operation("content.delete", "write", false), + ] +} +fn fixture() -> (Memory, Ed25519Signer, Vec) { + let signer = Ed25519Signer::from_seed(&[42; 32]); + let id = subject_id(&signer.public_key()); + let operations = operations(); + let subject = Subject { + resource_id: id.clone(), + kind: SubjectKind::Registered, + primary_group: "g_public".into(), + auth_version: 1, + local_only: false, + }; + let credential = Credential { + id: key_id(&signer.public_key()), + subject_id: id.clone(), + kind: CredentialKind::SigningKey, + verifier: Verifier(signer.public_key().to_vec()), + ceiling: vec![Grant { + capability: "resource.basic".into(), + version: 1, + scope: Scope { + resource_id: "r_root".into(), + descendants: true, + }, + operations: operations + .iter() + .map(|op| format!("{}@1", op.name)) + .collect(), + constraints: BTreeMap::new(), + }], + not_before: now(), + expires_at: Some(later(1800)), + revoked_at: None, + source_credential_id: None, + }; + let mut memory = Memory::default(); + memory.records.insert((0, id), wire(&subject).unwrap()); + memory + .records + .insert((1, credential.id.clone()), wire(&credential).unwrap()); + for (id, parent) in [("r_root", None), ("t_wiki", Some("r_root"))] { + memory.records.insert((3,id.into()),wire(&serde_json::json!({"id":id,"type":"topic","parent":parent,"owner":"u_owner","group":"g_public","mode":"0755","state":"active","revision":null})).unwrap()); + } + (memory, signer, operations) +} +fn principal(memory: &Memory, signer: &Ed25519Signer, operation: &OperationPolicy) -> Principal { + let mut raw = serde_json::json!({"request_id":"browser-approval","protocol_version":1,"operation":operation.name,"contract_version":1,"target_service":"https://fixture.test","subject":subject_id(&signer.public_key()),"arguments":{},"expected_generations":[],"expires_at":later(60),"payload_digest":format!("sha256:{}","0".repeat(64)),"proof":null,"return_fields":[],"source":"msg"}); + raw["payload_digest"] = Request::parse(&raw.to_string()) + .unwrap() + .payload_digest() + .unwrap() + .into(); + let signature = signer + .sign( + &Request::parse(&raw.to_string()) + .unwrap() + .signing_bytes() + .unwrap(), + "request", + ) + .unwrap(); + raw["proof"] = serde_json::json!({"signature":signature,"certificates":[]}); + let root_signer = Ed25519Signer::from_seed(&[1; 32]); + let validator = CertificateValidator::new( + Registry::new(vec![]).unwrap(), + root(&root_signer), + root_signer.public_key(), + "https://fixture.test".into(), + ) + .unwrap(); + let mut authenticator = AuthenticationService::new(validator, memory, vec![], vec![]).unwrap(); + match authenticator + .authenticate( + &Request::parse(&raw.to_string()).unwrap(), + operation, + memory, + Entry::Network, + now(), + ) + .unwrap() + { + Admission::Fresh(principal) => principal, + Admission::Replay(_) => panic!("unexpected replay"), + } +} +fn text<'a>(json: &'a Json, name: &str) -> &'a str { + json.as_object().unwrap()[name].as_str().unwrap() +} + +#[test] +fn signed_approval_is_one_use_limited_and_immediately_revocable() { + let (mut memory, signer, operations) = fixture(); + let principal = principal(&memory, &signer, &operations[0]); + let service = Service::new( + Config { + enabled: true, + session_ttl: 3600, + }, + "https://fixture.test", + &[9; 32], + &operations, + &[], + ) + .unwrap(); + let pending = service.pending(&mut memory, now()).unwrap(); + let value = text(&pending, "value"); + let code = text(&pending, "user_code"); + assert_eq!(code.len(), 8); + for state in memory.states.values() { + let stored = state.body.canonical().unwrap(); + assert!(!stored.contains(value)); + assert!(!stored.contains(code)); + } + assert_eq!( + text( + &service.inspect(&mut memory, code, now()).unwrap(), + "status" + ), + "pending" + ); + service + .approve(&mut memory, &principal, code, "approve", now()) + .unwrap(); + let logged = service.poll(&mut memory, value, now()).unwrap(); + let cookie = text(&logged, "cookie"); + assert_eq!( + logged.as_object().unwrap()["expires"], + wire(&later(1800)).unwrap() + ); + assert!(service.poll(&mut memory, value, later(5)).is_err()); + let bearer = service.credentials(&mut memory, cookie, now()).unwrap(); + let credential = memory.credential(&bearer.credential_id).unwrap(); + let grants: Vec<_> = credential + .ceiling + .iter() + .flat_map(|g| { + g.operations + .iter() + .map(move |op| (op.as_str(), g.scope.resource_id.as_str())) + }) + .collect(); + assert_eq!( + grants, + vec![ + ("content.post_get@1", "r_root"), + ("content.post_create@1", "t_wiki") + ] + ); + assert!(!b64(bearer.secret.as_ref()).is_empty()); + let source = memory_source(&memory, &bearer.credential_id); + assert!(service + .create_session(&mut memory, &source, Some(cookie), None, now()) + .is_err()); + service.revoke(&mut memory, cookie, now()).unwrap(); + assert!(service.credentials(&mut memory, cookie, now()).is_err()); +} + +fn memory_source(memory: &Memory, id: &str) -> Json { + memory + .credential_state(&format!("browser:{id}")) + .unwrap() + .unwrap() + .body +} + +#[test] +fn polling_backoff_denial_and_expiry_commit_only_bounded_state() { + let (mut memory, signer, operations) = fixture(); + let principal = principal(&memory, &signer, &operations[0]); + let service = Service::new( + Config { + enabled: true, + session_ttl: 3600, + }, + "https://fixture.test", + &[9; 32], + &operations, + &[], + ) + .unwrap(); + let pending = service.pending(&mut memory, now()).unwrap(); + let value = text(&pending, "value"); + let code = text(&pending, "user_code"); + assert_eq!( + text(&service.poll(&mut memory, value, now()).unwrap(), "error"), + "authorization_pending" + ); + assert_eq!( + text(&service.poll(&mut memory, value, now()).unwrap(), "error"), + "slow_down" + ); + service + .approve(&mut memory, &principal, code, "deny", now()) + .unwrap(); + assert_eq!( + text( + &service.poll(&mut memory, value, later(10)).unwrap(), + "error" + ), + "access_denied" + ); + assert!(service.poll(&mut memory, value, later(600)).is_err()); + assert!(service + .approve(&mut memory, &principal, code, "approve", later(10)) + .is_err()); + for _ in 0..120 { + service.rate(&mut memory, "127.0.0.1", now()).unwrap(); + } + assert!(matches!( + service.rate(&mut memory, "127.0.0.1", now()), + Err(Error("slow_down")) + )); + service.rate(&mut memory, "127.0.0.1", later(600)).unwrap(); + assert_eq!(memory.states.len(), 1); + memory.blocked = true; + assert!(matches!( + service.pending(&mut memory, now()), + Err(Error("recovery_quarantined")) + )); +} + +#[test] +fn parent_revocation_and_auth_version_changes_invalidate_live_sessions() { + for change_version in [false, true] { + let (mut memory, signer, operations) = fixture(); + let principal = principal(&memory, &signer, &operations[0]); + let service = Service::new( + Config { + enabled: true, + session_ttl: 3600, + }, + "https://fixture.test", + &[9; 32], + &operations, + &[], + ) + .unwrap(); + let pending = service.pending(&mut memory, now()).unwrap(); + service + .approve( + &mut memory, + &principal, + text(&pending, "user_code"), + "approve", + now(), + ) + .unwrap(); + let logged = service + .poll(&mut memory, text(&pending, "value"), now()) + .unwrap(); + let cookie = text(&logged, "cookie"); + service.credentials(&mut memory, cookie, now()).unwrap(); + if change_version { + let mut subject = memory.subject(&subject_id(&signer.public_key())).unwrap(); + subject.auth_version += 1; + memory + .records + .insert((0, subject.resource_id.clone()), wire(&subject).unwrap()); + } else { + let mut parent = memory.credential(&key_id(&signer.public_key())).unwrap(); + parent.revoked_at = Some(now()); + memory.save_credential(&parent, 1).unwrap(); + } + assert!(service.credentials(&mut memory, cookie, now()).is_err()); + assert!(memory.states.contains_key(&state_id("session", cookie))); + } +} diff --git a/rust/crates/msg-storage/src/content/files.rs b/rust/crates/msg-storage/src/content/files.rs index fad2d072..2806b463 100644 --- a/rust/crates/msg-storage/src/content/files.rs +++ b/rust/crates/msg-storage/src/content/files.rs @@ -147,7 +147,7 @@ pub(super) fn bounded(path: &Path, limit: usize) -> Result> { } Ok(bytes) } -fn unchanged(a: &Metadata, b: &Metadata) -> bool { +pub(super) fn unchanged(a: &Metadata, b: &Metadata) -> bool { ( a.dev(), a.ino(), diff --git a/rust/crates/msg-storage/src/content/lfs.rs b/rust/crates/msg-storage/src/content/lfs.rs new file mode 100644 index 00000000..0a2ca3cd --- /dev/null +++ b/rust/crates/msg-storage/src/content/lfs.rs @@ -0,0 +1,367 @@ +//! Shared LFS bytes. Repository hard links remain the authorization roots. +//! +//! Publication, migration and collection require the caller's metadata writer +//! fence and current authority. The additional shared-file lock serializes quota +//! accounting with Python workers. Reading never migrates a legacy object. +use super::files::{self, Temporary}; +use fs2::FileExt; +use msg_core::{Error, Result}; +use std::{ + fs::{self, File, Metadata, OpenOptions}, + io::{Read, Seek, SeekFrom, Write}, + os::unix::fs::{MetadataExt, OpenOptionsExt}, + path::{Path, PathBuf}, + thread, + time::{Duration, Instant, SystemTime}, +}; + +#[derive(Clone)] +pub struct LfsObjectStore { + root: PathBuf, + shared: PathBuf, + timeout: Duration, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct LfsMigration { + pub changed: bool, + pub size: u64, +} + +#[derive(Debug, Default, PartialEq, Eq)] +pub struct LfsCollection { + pub objects: u64, + pub bytes: u64, +} + +fn conflict(_: std::io::Error) -> Error { + Error("lfs_object_conflict") +} +fn oid(value: &str) -> Result<&str> { + if value.len() == 64 + && value + .bytes() + .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b)) + { + Ok(value) + } else { + Err(Error("invalid_lfs_oid")) + } +} +fn safe(path: &Path) -> Result { + files::absolute(path).map_err(|_| Error("lfs_object_conflict")) +} +fn check_directory(path: &Path) -> Result<()> { + safe(path)?; + files::check_directory(path, false).map_err(|_| Error("lfs_object_conflict")) +} +fn directory(path: &Path) -> Result<()> { + safe(path)?; + match fs::symlink_metadata(path) { + Ok(_) => check_directory(path), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + let parent = path.parent().ok_or(Error("lfs_object_conflict"))?; + directory(parent)?; + files::directory(path, false).map_err(|_| Error("lfs_object_conflict")) + } + Err(e) => Err(conflict(e)), + } +} +fn exists(path: &Path) -> Result { + match fs::symlink_metadata(path) { + Ok(_) => Ok(true), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false), + Err(e) => Err(conflict(e)), + } +} +fn size_valid(size: u64) -> Result<()> { + if size > i64::MAX as u64 { + Err(Error("lfs_size_mismatch")) + } else { + Ok(()) + } +} +fn copy_verified(path: &Path, digest: &str, size: u64, sink: &mut impl Write) -> Result { + size_valid(size)?; + safe(path)?; + let mut source = files::open(path).map_err(|e| match e.0 { + "content_missing" => Error("lfs_object_missing"), + _ => Error("lfs_object_conflict"), + })?; + let before = source.metadata().map_err(conflict)?; + if before.len() != size { + return Err(Error("lfs_size_mismatch")); + } + let (actual, length) = files::stream(&mut source, sink, size).map_err(|e| match e.0 { + "content_too_large" => Error("lfs_size_mismatch"), + _ => Error("lfs_object_conflict"), + })?; + let after = source.metadata().map_err(conflict)?; + if length != size { + return Err(Error("lfs_size_mismatch")); + } + if actual != format!("sha256:{digest}") { + return Err(Error("lfs_digest_mismatch")); + } + if !files::unchanged(&before, &after) { + return Err(Error("lfs_object_conflict")); + } + Ok(after) +} + +impl LfsObjectStore { + /// Configure service-owned paths without creating or migrating any object. + pub fn open(repo: &Path, shared: &Path, timeout: Duration) -> Result { + if Instant::now().checked_add(timeout).is_none() { + return Err(Error("invalid_lfs_lock_timeout")); + } + let store = Self { + root: safe(repo)?.join("lfs/objects"), + shared: safe(shared)?, + timeout, + }; + for path in [repo, &store.root, &store.shared] { + if exists(path)? { + check_directory(path)?; + } + } + Ok(store) + } + pub fn path(&self, digest: &str) -> Result { + let digest = oid(digest)?; + let path = self + .root + .join(&digest[..2]) + .join(&digest[2..4]) + .join(digest); + safe(&path) + } + /// A stat is deliberately read-only. It is not an integrity proof or a grant. + pub fn size(&self, digest: &str) -> Result> { + let path = self.path(digest)?; + match fs::symlink_metadata(path) { + Ok(meta) if meta.is_file() && !meta.file_type().is_symlink() => Ok(Some(meta.len())), + Ok(_) => Err(Error("lfs_object_conflict")), + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(e) => Err(conflict(e)), + } + } + /// Verify a private snapshot before emitting any bytes, including a range. + /// Temporary disk use is O(size); the caller enforces read concurrency limits. + pub fn read_to( + &self, + digest: &str, + size: u64, + range: Option<(u64, u64)>, + sink: &mut impl Write, + ) -> Result<()> { + let path = self.path(digest)?; + size_valid(size)?; + let (start, end) = range.unwrap_or((0, size)); + if start > end || end > size { + return Err(Error("invalid_byte_range")); + } + if self.size(digest)?.is_none() { + return Err(Error("lfs_object_missing")); + } + // A legacy repository can be read even before a shared store exists. + let staging = self.root.parent().ok_or(Error("lfs_object_conflict"))?; + let mut snapshot = Temporary::new(staging).map_err(|_| Error("lfs_object_conflict"))?; + copy_verified(&path, digest, size, &mut snapshot.file)?; + snapshot + .file + .seek(SeekFrom::Start(start)) + .map_err(conflict)?; + let copied = + std::io::copy(&mut (&mut snapshot.file).take(end - start), sink).map_err(conflict)?; + if copied != end - start { + return Err(Error("lfs_size_mismatch")); + } + Ok(()) + } + fn lock(&self) -> Result { + directory(&self.shared)?; + let path = self.shared.join(".msg-lfs.lock"); + let file = OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(false) + .mode(0o600) + .custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK) + .open(path) + .map_err(conflict)?; + let meta = file.metadata().map_err(conflict)?; + if !meta.is_file() || meta.nlink() != 1 || meta.mode() & 0o022 != 0 { + return Err(Error("lfs_object_conflict")); + } + let started = Instant::now(); + loop { + match FileExt::try_lock_exclusive(&file) { + Ok(()) => return Ok(file), + Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + if started.elapsed() >= self.timeout { + return Err(Error("server_busy")); + } + thread::sleep( + Duration::from_millis(10) + .min(self.timeout - started.elapsed().min(self.timeout)), + ); + } + Err(e) => return Err(conflict(e)), + } + } + } + /// Approximate without a writer fence; publication calls this under its lock. + pub fn shared_usage(&self) -> Result { + if !exists(&self.shared)? { + return Ok(0); + } + check_directory(&self.shared)?; + let mut total = 0_u64; + for entry in fs::read_dir(&self.shared).map_err(conflict)? { + let path = entry.map_err(conflict)?.path(); + if !path + .file_name() + .and_then(|s| s.to_str()) + .is_some_and(|s| oid(s).is_ok()) + { + continue; + } + let info = fs::symlink_metadata(path).map_err(conflict)?; + if !info.is_file() || info.file_type().is_symlink() { + return Err(Error("lfs_object_conflict")); + } + total = total + .checked_add(info.len()) + .ok_or(Error("lfs_deployment_capacity_exceeded"))?; + } + Ok(total) + } + fn publish_locked( + &self, + digest: &str, + staged: &Path, + size: u64, + quota: Option, + ) -> Result { + let destination = self.path(digest)?; + let shared = self.shared.join(digest); + copy_verified(staged, digest, size, &mut std::io::sink())?; + if exists(&destination)? { + copy_verified(&destination, digest, size, &mut std::io::sink())?; + } + let parent = destination.parent().ok_or(Error("lfs_object_conflict"))?; + directory(parent)?; + if fs::metadata(&self.shared).map_err(conflict)?.dev() + != fs::metadata(parent).map_err(conflict)?.dev() + { + return Err(Error("lfs_shared_volume_required")); + } + if exists(&shared)? { + copy_verified(&shared, digest, size, &mut std::io::sink())?; + } else { + if let Some(quota) = quota { + if self + .shared_usage()? + .checked_add(size) + .is_none_or(|n| n > quota) + { + return Err(Error("lfs_deployment_capacity_exceeded")); + } + } + let mut temporary = + Temporary::new(&self.shared).map_err(|_| Error("lfs_object_conflict"))?; + copy_verified(staged, digest, size, &mut temporary.file)?; + temporary.file.sync_all().map_err(conflict)?; + if !temporary + .install(&shared) + .map_err(|_| Error("lfs_object_conflict"))? + { + copy_verified(&shared, digest, size, &mut std::io::sink())?; + } + } + let shared_info = fs::symlink_metadata(&shared).map_err(conflict)?; + if let Ok(info) = fs::symlink_metadata(&destination) { + if (info.dev(), info.ino()) == (shared_info.dev(), shared_info.ino()) { + return Ok(false); + } + } + // Replace the repository link atomically. The canonical shared inode is + // never overwritten, and a legacy ACL root survives every crash point. + let temporary = Temporary::new(parent).map_err(|_| Error("lfs_object_conflict"))?; + fs::remove_file(&temporary.path).map_err(conflict)?; + fs::hard_link(&shared, &temporary.path).map_err(conflict)?; + fs::rename(&temporary.path, &destination).map_err(conflict)?; + files::sync_dir(parent).map_err(|_| Error("lfs_object_conflict"))?; + Ok(true) + } + /// A retained object after a metadata rollback is safe; never unlink shared + /// bytes as compensation for a failed or ambiguously committed publication. + pub fn publish( + &self, + digest: &str, + staged: &Path, + size: u64, + quota: Option, + ) -> Result { + oid(digest)?; + size_valid(size)?; + let _lock = self.lock()?; + self.publish_locked(digest, staged, size, quota) + } + pub fn migrate_legacy(&self, digest: &str, quota: Option) -> Result { + let source = self.path(digest)?; + let _lock = self.lock()?; + let size = self.size(digest)?.ok_or(Error("lfs_object_missing"))?; + let changed = self.publish_locked(digest, &source, size, quota)?; + Ok(LfsMigration { changed, size }) + } + /// The caller must first reconcile pending content work and hold the metadata + /// writer fence. `index` is the content-store index, not a request parameter. + pub fn collect_unlinked(&self, older_than: SystemTime, index: &Path) -> Result { + let index = safe(index)?; + if exists(&index)? { + check_directory(&index)?; + } + if !exists(&self.shared)? { + return Ok(LfsCollection::default()); + } + let _lock = self.lock()?; + let mut removed = LfsCollection::default(); + for entry in fs::read_dir(&self.shared).map_err(conflict)? { + let path = entry.map_err(conflict)?.path(); + let Some(name) = path + .file_name() + .and_then(|s| s.to_str()) + .filter(|s| oid(s).is_ok()) + else { + continue; + }; + let info = fs::symlink_metadata(&path).map_err(conflict)?; + if !info.is_file() || info.file_type().is_symlink() { + return Err(Error("lfs_object_conflict")); + } + if info.nlink() != 1 + || info.modified().map_err(conflict)? > older_than + || exists(&index.join(name))? + { + continue; + } + fs::remove_file(path).map_err(conflict)?; + removed.objects += 1; + removed.bytes = removed + .bytes + .checked_add(info.len()) + .ok_or(Error("lfs_object_conflict"))?; + } + if removed.objects > 0 { + files::sync_dir(&self.shared).map_err(|_| Error("lfs_object_conflict"))?; + } + Ok(removed) + } +} + +#[cfg(test)] +mod tests; diff --git a/rust/crates/msg-storage/src/content/lfs/tests.rs b/rust/crates/msg-storage/src/content/lfs/tests.rs new file mode 100644 index 00000000..db0bbbe3 --- /dev/null +++ b/rust/crates/msg-storage/src/content/lfs/tests.rs @@ -0,0 +1,280 @@ +use super::*; +use std::{ + fs::DirBuilder, + os::unix::fs::{symlink, DirBuilderExt}, + sync::{ + atomic::{AtomicU64, Ordering}, + Arc, Barrier, + }, + time::UNIX_EPOCH, +}; + +static NEXT: AtomicU64 = AtomicU64::new(0); +struct Fixture(PathBuf); +impl Fixture { + fn new() -> Self { + let path = std::env::temp_dir().join(format!( + "msg-lfs-{}-{}", + std::process::id(), + NEXT.fetch_add(1, Ordering::Relaxed) + )); + DirBuilder::new().mode(0o700).create(&path).unwrap(); + Self(path) + } + fn store(&self, name: &str) -> LfsObjectStore { + let repo = self.0.join(name); + DirBuilder::new().mode(0o700).create(&repo).unwrap(); + LfsObjectStore::open(&repo, &self.0.join("shared"), Duration::from_millis(40)).unwrap() + } + fn stage(&self, bytes: &[u8]) -> (String, PathBuf) { + let digest = files::hex(bytes); + let path = self.0.join(format!("staged-{digest}")); + fs::write(&path, bytes).unwrap(); + (digest, path) + } +} +impl Drop for Fixture { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } +} + +#[test] +fn repositories_share_one_verified_inode_and_only_charge_new_bytes() { + let f = Fixture::new(); + let a = f.store("a.git"); + let b = f.store("b.git"); + let data = b"shared\0bytes"; + let (digest, staged) = f.stage(data); + assert!(a + .publish(&digest, &staged, data.len() as u64, Some(data.len() as u64)) + .unwrap()); + let canonical = a.shared.join(&digest); + let inode = fs::metadata(&canonical).unwrap().ino(); + assert!(!a + .publish(&digest, &staged, data.len() as u64, Some(0)) + .unwrap()); + assert!(b + .publish(&digest, &staged, data.len() as u64, Some(0)) + .unwrap()); + assert_eq!(a.shared_usage().unwrap(), data.len() as u64); + for store in [&a, &b] { + let path = store.path(&digest).unwrap(); + assert!(path.ends_with( + Path::new("lfs/objects") + .join(&digest[..2]) + .join(&digest[2..4]) + .join(&digest) + )); + assert_eq!(fs::metadata(&path).unwrap().ino(), inode); + assert_eq!(store.size(&digest).unwrap(), Some(data.len() as u64)); + let mut read = Vec::new(); + store + .read_to(&digest, data.len() as u64, Some((2, 9)), &mut read) + .unwrap(); + assert_eq!(read, data[2..9]); + } + assert_eq!(fs::metadata(canonical).unwrap().nlink(), 3); + let (other, source) = f.stage(b"extra"); + assert_eq!( + a.publish(&other, &source, 5, Some(data.len() as u64)) + .unwrap_err(), + Error("lfs_deployment_capacity_exceeded") + ); + assert_eq!(a.size(&other).unwrap(), None); +} + +#[test] +fn legacy_reads_do_not_create_shared_store_and_migration_is_explicit() { + let f = Fixture::new(); + let store = f.store("legacy.git"); + let (digest, _) = f.stage(b"legacy"); + let path = store.path(&digest).unwrap(); + directory(path.parent().unwrap()).unwrap(); + fs::write(&path, b"legacy").unwrap(); + let original = fs::metadata(&path).unwrap().ino(); + assert_eq!(store.size(&digest).unwrap(), Some(6)); + let mut read = Vec::new(); + store.read_to(&digest, 6, None, &mut read).unwrap(); + assert_eq!(read, b"legacy"); + assert!(!store.shared.exists()); + assert_eq!(fs::metadata(&path).unwrap().ino(), original); + assert_eq!( + store.migrate_legacy(&digest, Some(6)).unwrap(), + LfsMigration { + changed: true, + size: 6 + } + ); + let shared = fs::metadata(store.shared.join(&digest)).unwrap(); + assert_eq!(fs::metadata(&path).unwrap().ino(), shared.ino()); + assert_ne!(shared.ino(), original); + assert_eq!( + store.migrate_legacy(&digest, Some(0)).unwrap(), + LfsMigration { + changed: false, + size: 6 + } + ); + assert_eq!(fs::read(path).unwrap(), b"legacy"); +} + +#[test] +fn corruption_and_bad_sizes_never_publish_or_reach_a_read_sink() { + let f = Fixture::new(); + let store = f.store("a.git"); + let (digest, source) = f.stage(b"correct"); + for size in [0, 6, 8, u64::MAX] { + assert_eq!( + store.publish(&digest, &source, size, None).unwrap_err(), + Error("lfs_size_mismatch") + ); + assert_eq!(store.size(&digest).unwrap(), None); + } + store.publish(&digest, &source, 7, None).unwrap(); + let canonical = store.shared.join(&digest); + let inode = fs::metadata(&canonical).unwrap().ino(); + fs::write(&source, b"corrupt").unwrap(); + assert_eq!( + store.publish(&digest, &source, 7, None).unwrap_err(), + Error("lfs_digest_mismatch") + ); + assert_eq!(fs::metadata(&canonical).unwrap().ino(), inode); + assert_eq!(fs::read(&canonical).unwrap(), b"correct"); + fs::write(&canonical, b"corrupt").unwrap(); + let mut sink = Vec::new(); + assert_eq!( + store + .read_to(&digest, 7, Some((0, 0)), &mut sink) + .unwrap_err(), + Error("lfs_digest_mismatch") + ); + assert!(sink.is_empty()); + assert_eq!( + store + .read_to(&digest, 7, Some((4, 3)), &mut sink) + .unwrap_err(), + Error("invalid_byte_range") + ); +} + +#[test] +fn paths_reject_symlinks_and_non_objects() { + let f = Fixture::new(); + let store = f.store("a.git"); + for value in [ + "../escape".to_owned(), + "A".repeat(64), + "0".repeat(63), + "0".repeat(65), + ] { + assert_eq!(store.size(&value).unwrap_err(), Error("invalid_lfs_oid")); + } + let (digest, source) = f.stage(b"abc"); + let link = f.0.join("source-link"); + symlink(&source, &link).unwrap(); + assert_eq!( + store.publish(&digest, &link, 3, None).unwrap_err(), + Error("lfs_object_conflict") + ); + let path = store.path(&digest).unwrap(); + directory(path.parent().unwrap()).unwrap(); + symlink(&source, &path).unwrap(); + assert_eq!( + store.size(&digest).unwrap_err(), + Error("lfs_object_conflict") + ); + assert_eq!( + store.publish(&digest, &source, 3, None).unwrap_err(), + Error("lfs_object_conflict") + ); + fs::remove_file(path).unwrap(); + symlink(&source, store.shared.join(&digest)).unwrap(); + assert_eq!( + store.publish(&digest, &source, 3, None).unwrap_err(), + Error("lfs_object_conflict") + ); + assert_eq!( + store.shared_usage().unwrap_err(), + Error("lfs_object_conflict") + ); +} + +#[test] +fn collector_keeps_repository_links_index_references_and_recent_objects() { + let f = Fixture::new(); + let store = f.store("a.git"); + let index = f.0.join("index"); + directory(&index).unwrap(); + let mut digests = Vec::new(); + for data in [b"linked".as_slice(), b"indexed", b"orphan"] { + let (digest, source) = f.stage(data); + store + .publish(&digest, &source, data.len() as u64, None) + .unwrap(); + digests.push(digest); + } + fs::remove_file(store.path(&digests[1]).unwrap()).unwrap(); + fs::remove_file(store.path(&digests[2]).unwrap()).unwrap(); + fs::write(index.join(&digests[1]), b"index").unwrap(); + assert_eq!( + store.collect_unlinked(UNIX_EPOCH, &index).unwrap(), + LfsCollection::default() + ); + let future = SystemTime::now() + Duration::from_secs(1); + assert_eq!( + store.collect_unlinked(future, &index).unwrap(), + LfsCollection { + objects: 1, + bytes: 6 + } + ); + assert!(store.shared.join(&digests[0]).exists()); + assert!(store.shared.join(&digests[1]).exists()); + assert!(!store.shared.join(&digests[2]).exists()); + fs::remove_file(store.path(&digests[0]).unwrap()).unwrap(); + fs::remove_file(index.join(&digests[1])).unwrap(); + assert_eq!( + store.collect_unlinked(future, &index).unwrap(), + LfsCollection { + objects: 2, + bytes: 13 + } + ); +} + +#[test] +fn python_compatible_lock_serializes_publication_and_capacity() { + let f = Fixture::new(); + let mut a = f.store("a.git"); + let mut b = f.store("b.git"); + let first = f.stage(b"first"); + let second = f.stage(b"other"); + let held = a.lock().unwrap(); + assert_eq!( + b.publish(&first.0, &first.1, 5, Some(5)).unwrap_err(), + Error("server_busy") + ); + drop(held); + a.timeout = Duration::from_secs(2); + b.timeout = Duration::from_secs(2); + let barrier = Arc::new(Barrier::new(2)); + let mut children = Vec::new(); + for (store, (digest, source)) in [(a.clone(), first), (b, second)] { + let barrier = Arc::clone(&barrier); + children.push(thread::spawn(move || { + barrier.wait(); + store.publish(&digest, &source, 5, Some(5)) + })); + } + let results: Vec<_> = children.into_iter().map(|t| t.join().unwrap()).collect(); + assert_eq!(results.iter().filter(|r| r.is_ok()).count(), 1); + assert_eq!( + results + .iter() + .filter(|r| **r == Err(Error("lfs_deployment_capacity_exceeded"))) + .count(), + 1 + ); + assert_eq!(a.shared_usage().unwrap(), 5); +} diff --git a/rust/crates/msg-storage/src/content/mod.rs b/rust/crates/msg-storage/src/content/mod.rs index 44ae385a..7e5214da 100644 --- a/rust/crates/msg-storage/src/content/mod.rs +++ b/rust/crates/msg-storage/src/content/mod.rs @@ -7,10 +7,14 @@ //! that fence; abandoning an async blocking task is not cancellation. //! //! Files and Git refs are durable before successful return. This is NOT an -//! atomic metadata/content commit or a durable cross-store recovery journal. +//! atomic metadata/content commit. Use `content_transaction` for durable pin +//! recovery through a metadata commit witness; raw methods remain primitives. //! Errors may leave unreferenced content, which must not be blindly deleted. mod files; mod git; +mod lfs; +pub use lfs::{LfsCollection, LfsMigration, LfsObjectStore}; +pub mod recovery; #[cfg(test)] mod tests; use crate::records::{encode, BlobRef, Revision}; @@ -44,6 +48,7 @@ impl Default for ContentOptions { } } } +#[derive(Clone)] pub struct GitContentStore { root: PathBuf, repo: PathBuf, diff --git a/rust/crates/msg-storage/src/content/recovery.rs b/rust/crates/msg-storage/src/content/recovery.rs new file mode 100644 index 00000000..0fd4d19f --- /dev/null +++ b/rust/crates/msg-storage/src/content/recovery.rs @@ -0,0 +1,423 @@ +//! Durable undo for content pins, coupled to a metadata commit witness. +//! +//! `content_transaction` takes the existing metadata writer fence, reconciles +//! the previous journal, then runs one unit of work. Each pin's previous state +//! reaches disk before it changes. The final generation is written in the same +//! SQL transaction as the domain rows. After a crash, a new fenced transaction +//! can distinguish a committed generation from a rolled-back generation. +//! +//! Immutable blobs and revision objects are retained on failure; this module +//! never guesses that an object is safe to delete. All native content writers +//! and collectors must use this wrapper. Do not run old Python workers alongside +//! a pending native journal. This is not recovery of mutable Git receive-pack +//! refs, a restored authority snapshot, or arbitrary external effects. +use super::{files, GitContentStore, LfsMigration, LfsObjectStore}; +use crate::{ + postgres::{PgFailure, PgSession, PgStore}, + records::{decode, encode, BlobRef, Revision}, + SqliteWriter, WriteFailure, WriteSession, +}; +use msg_core::{Error, Json, Result, MAX_BYTES}; +use msg_identity::store::AuthorityStore; +use serde::{Deserialize, Deserializer, Serialize}; +use std::{fs, io::Read, os::unix::fs::MetadataExt, path::Path}; + +const GENERATION: &str = "content_recovery_generation"; +const RUNTIME_GENERATION: &str = "recovery_runtime_generation"; +const JOURNAL: &str = "content-recovery.json"; +const MAX_ACTIONS: usize = 4096; +const MAX_LEASE_BYTES: usize = 1024; +const RECOVERY_LEASE_PREFIX: &str = "msg:content-recovery:"; + +mod sealed { + pub trait Metadata {} + impl Metadata for crate::WriteSession<'_> {} + impl Metadata for &crate::postgres::PgSession {} +} + +/// Only the two transaction-owning adapters implement this interface. +pub trait ContentMetadata: sealed::Metadata { + fn content_require_write(&self) -> Result<()>; + fn content_setting(&self, key: &str) -> Result>; + fn content_has_setting(&self, key: &str) -> Result; + fn content_set_generation(&mut self, value: &Json) -> Result<()>; + fn content_on_rollback(&mut self, effect: Box Result<()>>) -> Result<()>; +} +impl ContentMetadata for WriteSession<'_> { + fn content_require_write(&self) -> Result<()> { + self.active() + } + fn content_setting(&self, key: &str) -> Result> { + self.setting(key) + } + fn content_has_setting(&self, key: &str) -> Result { + self.has_setting(key) + } + fn content_set_generation(&mut self, value: &Json) -> Result<()> { + self.set_setting(GENERATION, value) + } + fn content_on_rollback(&mut self, effect: Box Result<()>>) -> Result<()> { + self.on_rollback(effect) + } +} +impl ContentMetadata for &PgSession { + fn content_require_write(&self) -> Result<()> { + self.require_write() + } + fn content_setting(&self, key: &str) -> Result> { + self.setting(key) + } + fn content_has_setting(&self, key: &str) -> Result { + self.has_setting(key) + } + fn content_set_generation(&mut self, value: &Json) -> Result<()> { + self.set_setting(GENERATION, value) + } + fn content_on_rollback(&mut self, effect: Box Result<()>>) -> Result<()> { + self.on_rollback(effect) + } +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct UndoPin { + blob: BlobRef, + lease: String, + pinned: bool, +} +impl UndoPin { + fn apply(&self, content: &GitContentStore) -> Result<()> { + if self.pinned { + content.pin(&self.blob, &self.lease) + } else { + content.unpin(&self.blob, &self.lease) + } + } +} +#[derive(Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct Journal { + format_version: u8, + #[serde(deserialize_with = "required_option")] + before: Option, + after: i64, + #[serde(deserialize_with = "required_option")] + runtime_generation: Option, + undo: Vec, +} +fn recovery_required(_: T) -> Error { + Error("storage_recovery_required") +} +fn required_option<'de, T: Deserialize<'de>, D: Deserializer<'de>>( + deserializer: D, +) -> std::result::Result, D::Error> { + Option::::deserialize(deserializer) +} +fn generation(metadata: &impl ContentMetadata) -> Result> { + metadata + .content_setting(GENERATION)? + .map(|value| { + let number = value + .as_integer() + .map_err(recovery_required)? + .parse::() + .map_err(recovery_required)?; + if number < 1 { + return Err(Error("storage_recovery_required")); + } + Ok(number) + }) + .transpose() +} +fn runtime_generation(metadata: &impl ContentMetadata) -> Result> { + metadata + .content_setting(RUNTIME_GENERATION)? + .map(|value| { + let value = value.as_str().map_err(recovery_required)?; + if value.is_empty() { + return Err(Error("storage_recovery_required")); + } + Ok(value.to_owned()) + }) + .transpose() +} +impl Journal { + fn lease(&self) -> String { + format!("{RECOVERY_LEASE_PREFIX}{}", self.after) + } + fn validate(&self) -> Result<()> { + if self.format_version != 1 + || self.before.is_some_and(|value| value < 1) + || self.before.unwrap_or(0).checked_add(1) != Some(self.after) + || self + .runtime_generation + .as_ref() + .is_some_and(String::is_empty) + || self.undo.len() > MAX_ACTIONS + { + return Err(Error("storage_recovery_required")); + } + for undo in &self.undo { + files::key(&undo.blob.digest).map_err(recovery_required)?; + if undo.blob.size < 0 + || undo.lease.len() > MAX_LEASE_BYTES + || undo.lease.starts_with(RECOVERY_LEASE_PREFIX) + { + return Err(Error("storage_recovery_required")); + } + } + Ok(()) + } + fn persist(&self, content: &GitContentStore) -> Result<()> { + self.validate()?; + let bytes = encode(self) + .and_then(|value| value.canonical()) + .map_err(|_| Error("content_transaction_too_large"))?; + files::replace(&content.root.join(JOURNAL), bytes.as_bytes(), false) + .map_err(recovery_required) + } +} + +fn recover(metadata: &impl ContentMetadata, content: &GitContentStore) -> Result<()> { + metadata.content_require_write()?; + if metadata.content_has_setting("recovery_quarantine")? { + return Err(Error("recovery_quarantined")); + } + content.layout()?; + let path = content.root.join(JOURNAL); + let file = match files::open(&path) { + Ok(file) => file, + Err(Error("content_missing")) => return Ok(()), + Err(error) => return Err(recovery_required(error)), + }; + let info = file.metadata().map_err(recovery_required)?; + if info.nlink() != 1 || info.mode() & 0o077 != 0 || info.len() > MAX_BYTES as u64 { + return Err(Error("storage_recovery_required")); + } + let mut bytes = String::new(); + file.take(MAX_BYTES as u64 + 1) + .read_to_string(&mut bytes) + .map_err(recovery_required)?; + let journal: Journal = Json::parse(&bytes) + .and_then(|value| decode(&value)) + .map_err(recovery_required)?; + journal.validate()?; + if journal.runtime_generation != runtime_generation(metadata)? { + return Err(Error("storage_recovery_required")); + } + let current = generation(metadata)?; + if current == journal.before { + // Keeping the complete journal until every inverse is durable makes + // a second crash during recovery safe: each inverse is idempotent. + for undo in journal.undo.iter().rev() { + undo.apply(content).map_err(recovery_required)?; + } + } else if current != Some(journal.after) { + return Err(Error("storage_recovery_required")); + } + // Old pinned bytes remain protected even if collection runs later in the + // same SQL transaction that removes their domain pins. Release these holds + // only after the metadata commit outcome has been established. + for undo in &journal.undo { + if undo.pinned { + content + .unpin(&undo.blob, &journal.lease()) + .map_err(recovery_required)?; + } + } + fs::remove_file(path).map_err(recovery_required)?; + files::sync_dir(&content.root).map_err(recovery_required) +} + +/// Content changes and their metadata share one adapter-owned transaction. +/// `metadata()` permits domain reads/writes; commit rights stay with the adapter. +/// Errors in content methods remain sticky even when a caller catches them. +pub struct ContentTransaction<'a, M: ContentMetadata> { + metadata: &'a mut M, + content: &'a GitContentStore, + journal: Journal, + persisted: bool, + failed: Option, +} +impl ContentTransaction<'_, M> { + pub fn metadata(&mut self) -> &mut M { + self.metadata + } + fn change(&mut self, body: impl FnOnce(&mut Self) -> Result) -> Result { + if let Some(error) = self.failed { + return Err(error); + } + let result = body(self); + if let Err(error) = result { + self.failed = Some(error); + Err(error) + } else { + result + } + } + fn persist(&mut self) -> Result<()> { + self.journal.persist(self.content)?; + self.persisted = true; + Ok(()) + } + fn begin_external(&mut self) -> Result<()> { + if !self.persisted { + self.persist()?; + } + Ok(()) + } + pub fn put( + &mut self, + source: &mut impl Read, + media_type: &str, + expected: Option<&str>, + limit: u64, + ) -> Result { + self.change(|unit| { + unit.begin_external()?; + unit.content.put(source, media_type, expected, limit) + }) + } + pub fn put_bytes( + &mut self, + bytes: &[u8], + media_type: &str, + expected: Option<&str>, + ) -> Result { + self.put(&mut &bytes[..], media_type, expected, bytes.len() as u64) + } + fn change_pin(&mut self, blob: &BlobRef, lease: &str, pinned: bool) -> Result<()> { + self.change(|unit| { + if lease.starts_with(RECOVERY_LEASE_PREFIX) { + return Err(Error("invalid_content_lease")); + } + if lease.len() > MAX_LEASE_BYTES || unit.journal.undo.len() >= MAX_ACTIONS { + return Err(Error("content_transaction_too_large")); + } + let undo = UndoPin { + blob: blob.clone(), + lease: lease.to_owned(), + pinned: unit.content.pinned(blob, lease)?, + }; + unit.journal.undo.push(undo.clone()); + unit.persist()?; + if undo.pinned { + unit.content.pin(blob, &unit.journal.lease())?; + } + let content = GitContentStore::clone(unit.content); + unit.metadata + .content_on_rollback(Box::new(move || undo.apply(&content)))?; + if pinned { + unit.content.pin(blob, lease) + } else { + unit.content.unpin(blob, lease) + } + }) + } + pub fn pin(&mut self, blob: &BlobRef, lease: &str) -> Result<()> { + self.change_pin(blob, lease, true) + } + pub fn unpin(&mut self, blob: &BlobRef, lease: &str) -> Result<()> { + self.change_pin(blob, lease, false) + } + pub fn commit_revision(&mut self, topic: &str, revision: &Revision) -> Result { + self.change(|unit| { + unit.begin_external()?; + unit.content.commit_revision(topic, revision) + }) + } + pub fn publish_lfs( + &mut self, + lfs: &LfsObjectStore, + digest: &str, + staged: &Path, + size: u64, + quota: Option, + ) -> Result { + self.change(|unit| { + unit.begin_external()?; + lfs.publish(digest, staged, size, quota) + }) + } + pub fn migrate_lfs( + &mut self, + lfs: &LfsObjectStore, + digest: &str, + quota: Option, + ) -> Result { + self.change(|unit| { + unit.begin_external()?; + lfs.migrate_legacy(digest, quota) + }) + } + fn finish(&mut self) -> Result<()> { + if let Some(error) = self.failed { + return Err(error); + } + if generation(self.metadata)? != self.journal.before + || runtime_generation(self.metadata)? != self.journal.runtime_generation + || self.metadata.content_has_setting("recovery_quarantine")? + { + return Err(Error("storage_recovery_required")); + } + if self.persisted { + self.metadata + .content_set_generation(&Json::parse(&self.journal.after.to_string())?)?; + } + Ok(()) + } +} + +fn run( + metadata: &mut M, + content: &GitContentStore, + body: impl FnOnce(&mut ContentTransaction<'_, M>) -> Result, +) -> Result { + recover(metadata, content)?; + let before = generation(metadata)?; + let mut unit = ContentTransaction { + journal: Journal { + format_version: 1, + before, + after: before + .unwrap_or(0) + .checked_add(1) + .ok_or(Error("storage_recovery_required"))?, + runtime_generation: runtime_generation(metadata)?, + undo: Vec::new(), + }, + metadata, + content, + persisted: false, + failed: None, + }; + let result = body(&mut unit)?; + unit.finish()?; + Ok(result) +} + +impl SqliteWriter { + /// An empty body performs startup recovery before admitting content work. + pub fn content_transaction( + &mut self, + content: &GitContentStore, + body: impl FnOnce(&mut ContentTransaction<'_, WriteSession<'_>>) -> Result, + ) -> std::result::Result { + self.transaction(|tx| run(tx, content, body)) + } +} +impl PgStore { + pub fn content_transaction( + &self, + content: &GitContentStore, + body: impl FnOnce(&mut ContentTransaction<'_, &PgSession>) -> Result, + ) -> std::result::Result { + self.transaction(true, |tx| { + let mut metadata = tx; + run(&mut metadata, content, body) + }) + } +} + +#[cfg(test)] +mod tests; diff --git a/rust/crates/msg-storage/src/content/recovery/tests.rs b/rust/crates/msg-storage/src/content/recovery/tests.rs new file mode 100644 index 00000000..683ff322 --- /dev/null +++ b/rust/crates/msg-storage/src/content/recovery/tests.rs @@ -0,0 +1,373 @@ +use super::*; +use crate::content::ContentOptions; +use rusqlite::{Connection, OptionalExtension}; +use std::{ + fs::DirBuilder, + os::unix::{ + fs::{symlink, DirBuilderExt}, + process::ExitStatusExt, + }, + path::{Path, PathBuf}, + process::{Child, Command, Stdio}, + sync::atomic::{AtomicU64, Ordering}, + thread, + time::{Duration, Instant}, +}; + +const DATA: &[u8] = b"durable content"; +static NEXT: AtomicU64 = AtomicU64::new(0); +struct Fixture(PathBuf); +impl Fixture { + fn new() -> Self { + let path = std::env::temp_dir().join(format!( + "msg-content-recovery-{}-{}", + std::process::id(), + NEXT.fetch_add(1, Ordering::Relaxed) + )); + DirBuilder::new().mode(0o700).create(&path).unwrap(); + let fixture = Self(path); + Connection::open(fixture.db()).unwrap().execute_batch( + "PRAGMA journal_mode=WAL; + CREATE TABLE schema_version(version INTEGER PRIMARY KEY); INSERT INTO schema_version VALUES(1); + CREATE TABLE settings(key TEXT PRIMARY KEY,value TEXT NOT NULL); + CREATE TABLE identities(id TEXT PRIMARY KEY,kind TEXT,body TEXT); + CREATE TABLE credentials(id TEXT PRIMARY KEY,subject TEXT,body TEXT); + CREATE TABLE certificates(id TEXT PRIMARY KEY,subject TEXT,parent TEXT,revoked INTEGER,body TEXT); + CREATE TABLE resources(id TEXT PRIMARY KEY,type TEXT,name TEXT,parent TEXT REFERENCES resources(id) DEFERRABLE INITIALLY DEFERRED,owner TEXT,grp TEXT,mode INTEGER,generation INTEGER,revision TEXT,state TEXT,created_at TEXT,modified_at TEXT,body TEXT,UNIQUE(parent,name)); + CREATE UNIQUE INDEX one_root ON resources((1)) WHERE parent IS NULL; + CREATE TABLE results(subject TEXT,request_id TEXT,digest TEXT,body TEXT,PRIMARY KEY(subject,request_id)); + CREATE TABLE credential_states(id TEXT PRIMARY KEY,kind TEXT,expires TEXT,body TEXT); + CREATE TABLE custodial_vault(subject TEXT PRIMARY KEY,signing_key_id TEXT,status TEXT); + CREATE TABLE token_deliveries(credential_id TEXT,subject TEXT,recovery_verifier TEXT,recovery_expires_at TEXT,consumed_at TEXT,request_id TEXT); + CREATE TABLE resource_tags(resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,tag TEXT,PRIMARY KEY(tag,resource_id)); + CREATE TABLE resource_path_aliases(parent_id TEXT REFERENCES resources(id) ON DELETE CASCADE,name TEXT,resource_id TEXT REFERENCES resources(id) ON DELETE CASCADE,PRIMARY KEY(parent_id,name)); + CREATE TABLE revisions(id TEXT PRIMARY KEY,resource_id TEXT REFERENCES resources(id),created_at TEXT,body TEXT); + CREATE TABLE relations(revision_id TEXT REFERENCES revisions(id) ON DELETE CASCADE,source_id TEXT,type TEXT,target_id TEXT,target_revision TEXT,body TEXT); + CREATE TABLE events(seq INTEGER PRIMARY KEY AUTOINCREMENT,id TEXT UNIQUE,body TEXT); + CREATE TABLE audit(seq INTEGER PRIMARY KEY AUTOINCREMENT,digest TEXT UNIQUE,previous TEXT,body TEXT); + CREATE TRIGGER audit_no_update BEFORE UPDATE ON audit BEGIN SELECT RAISE(ABORT,'append_only_audit'); END; + CREATE TRIGGER audit_no_delete BEFORE DELETE ON audit BEGIN SELECT RAISE(ABORT,'append_only_audit'); END;", + ).unwrap(); + fixture + } + fn db(&self) -> PathBuf { + self.0.join("metadata.db") + } + fn writer(&self) -> SqliteWriter { + SqliteWriter::open_existing(&self.db(), Duration::from_secs(1)).unwrap() + } + fn store(&self) -> GitContentStore { + GitContentStore::open(&self.0.join("store"), ContentOptions::default()).unwrap() + } + fn setting(&self, key: &str) -> Option { + Connection::open(self.db()) + .unwrap() + .query_row("SELECT value FROM settings WHERE key=?", [key], |row| { + row.get(0) + }) + .optional() + .unwrap() + } + fn set(&self, key: &str, value: &str) { + Connection::open(self.db()).unwrap().execute( + "INSERT INTO settings(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", + [key, value], + ).unwrap(); + } +} +impl Drop for Fixture { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } +} +fn blob(media: &str) -> BlobRef { + BlobRef { + digest: msg_core::digest(DATA), + size: DATA.len() as i64, + media_type: media.to_owned(), + } +} + +#[test] +fn committed_pin_removal_retains_recovery_hold_until_next_fenced_transaction() { + let f = Fixture::new(); + let content = f.store(); + let blob = content + .put_bytes(DATA, "application/octet-stream", None) + .unwrap(); + content.pin(&blob, "old").unwrap(); + f.writer() + .content_transaction(&content, |unit| unit.unpin(&blob, "old")) + .unwrap(); + assert!(!content.pinned(&blob, "old").unwrap()); + assert!(content.pinned(&blob, "msg:content-recovery:1").unwrap()); + assert_eq!(f.setting(GENERATION).as_deref(), Some("1")); + assert!(content.root.join(JOURNAL).exists()); + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap(); + assert!(!content.pinned(&blob, "old").unwrap()); + assert!(!content.pinned(&blob, "msg:content-recovery:1").unwrap()); + assert!(!content.root.join(JOURNAL).exists()); +} + +#[test] +fn rollback_and_repeated_recovery_restore_pins_in_reverse_order() { + let f = Fixture::new(); + let content = f.store(); + let blob = content.put_bytes(DATA, "text/plain", None).unwrap(); + content.pin(&blob, "old").unwrap(); + let error = f + .writer() + .content_transaction(&content, |unit| { + unit.unpin(&blob, "old")?; + unit.pin(&blob, "old")?; + unit.unpin(&blob, "old")?; + unit.pin(&blob, "new")?; + unit.metadata() + .set_setting("domain-row", &Json::string("uncommitted"))?; + Err::<(), _>(Error("rejected")) + }) + .unwrap_err(); + assert_eq!(error.cause, Error("rejected")); + assert!(error.cleanup_errors.is_empty()); + assert!(content.pinned(&blob, "old").unwrap()); + assert!(!content.pinned(&blob, "new").unwrap()); + assert_eq!(f.setting("domain-row"), None); + assert_eq!(f.setting(GENERATION), None); + for _ in 0..2 { + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap(); + assert!(content.pinned(&blob, "old").unwrap()); + assert!(!content.pinned(&blob, "new").unwrap()); + } + assert!(!content.pinned(&blob, "msg:content-recovery:1").unwrap()); +} + +#[test] +fn caught_content_errors_cannot_commit_domain_rows() { + let f = Fixture::new(); + let content = f.store(); + let error = f + .writer() + .content_transaction(&content, |unit| { + let expected = msg_core::digest(b"other bytes"); + assert_eq!( + unit.put_bytes(DATA, "text/plain", Some(&expected)).err(), + Some(Error("digest_mismatch")) + ); + unit.metadata() + .set_setting("domain-row", &Json::string("must roll back")) + }) + .unwrap_err(); + assert_eq!(error.cause, Error("digest_mismatch")); + assert_eq!(f.setting("domain-row"), None); + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap(); + assert!(!content.root.join(JOURNAL).exists()); + let blob = content.put_bytes(DATA, "text/plain", None).unwrap(); + content.pin(&blob, "msg:content-recovery:1").unwrap(); + assert_eq!( + f.writer() + .content_transaction(&content, |unit| unit.unpin(&blob, "msg:content-recovery:1")) + .unwrap_err() + .cause, + Error("invalid_content_lease") + ); + assert!(content.pinned(&blob, "msg:content-recovery:1").unwrap()); +} + +#[test] +fn corrupt_unknown_and_restored_journals_fail_closed_across_reopen() { + let f = Fixture::new(); + let content = f.store(); + let blob = content + .put_bytes(DATA, "application/octet-stream", None) + .unwrap(); + let path = content.root.join(JOURNAL); + let valid = + r#"{"format_version":1,"before":null,"after":1,"runtime_generation":null,"undo":[]}"#; + for bytes in [ + "{}", + r#"{"format_version":1,"before":null,"after":1,"after":1,"runtime_generation":null,"undo":[]}"#, + r#"{"format_version":1,"before":null,"after":1,"undo":[]}"#, + r#"{"format_version":1,"before":null,"after":1,"runtime_generation":null,"undo":[],"extra":true}"#, + r#"{"format_version":1,"before":null,"after":2,"runtime_generation":null,"undo":[]}"#, + ] { + files::replace(&path, bytes.as_bytes(), false).unwrap(); + for _ in 0..2 { + assert_eq!( + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap_err() + .cause, + Error("storage_recovery_required") + ); + assert_eq!(fs::read_to_string(&path).unwrap(), bytes); + } + } + files::replace(&path, valid.as_bytes(), false).unwrap(); + f.set(GENERATION, "9"); + assert_eq!( + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap_err() + .cause, + Error("storage_recovery_required") + ); + f.set(GENERATION, "1"); + f.set(RUNTIME_GENERATION, "\"restored-generation\""); + assert_eq!( + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap_err() + .cause, + Error("storage_recovery_required") + ); + f.set("recovery_quarantine", "null"); + assert_eq!( + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap_err() + .cause, + Error("recovery_quarantined") + ); + assert_eq!(content.read_bytes(&blob, 1024).unwrap(), DATA); + assert!(path.exists()); +} + +#[test] +fn journal_paths_reject_symlinks_and_extra_hard_links() { + let f = Fixture::new(); + let content = f.store(); + f.writer() + .content_transaction(&content, |unit| { + unit.put_bytes(DATA, "application/octet-stream", None) + .map(|_| ()) + }) + .unwrap(); + let path = content.root.join(JOURNAL); + let extra = f.0.join("extra-journal"); + fs::hard_link(&path, &extra).unwrap(); + assert_eq!( + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap_err() + .cause, + Error("storage_recovery_required") + ); + fs::remove_file(&path).unwrap(); + symlink(&extra, &path).unwrap(); + assert_eq!( + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap_err() + .cause, + Error("storage_recovery_required") + ); + assert!(extra.exists()); +} + +struct Running(Child); +impl Drop for Running { + fn drop(&mut self) { + let _ = self.0.kill(); + let _ = self.0.wait(); + } +} +fn killed_child(root: &Path, case: &str, media: &str) { + let mut child = Running( + Command::new(std::env::current_exe().unwrap()) + .args([ + "--exact", + "content::recovery::tests::crash_child", + "--ignored", + "--nocapture", + ]) + .env("MSG_CONTENT_CRASH_ROOT", root) + .env("MSG_CONTENT_CRASH_CASE", case) + .env("MSG_CONTENT_CRASH_MEDIA", media) + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .unwrap(), + ); + let started = Instant::now(); + while !root.join("ready").exists() { + assert!( + child.0.try_wait().unwrap().is_none(), + "crash fixture exited early" + ); + assert!( + started.elapsed() < Duration::from_secs(10), + "crash fixture did not become ready" + ); + thread::sleep(Duration::from_millis(10)); + } + child.0.kill().unwrap(); + assert_eq!(child.0.wait().unwrap().signal(), Some(9)); +} + +#[test] +fn sigkill_before_and_after_sql_commit_recover_binary_and_git_pins() { + for media in ["application/octet-stream", "text/plain"] { + for case in ["before-commit", "after-commit"] { + let f = Fixture::new(); + let content = f.store(); + let blob = content.put_bytes(DATA, media, None).unwrap(); + content.pin(&blob, "old").unwrap(); + killed_child(&f.0, case, media); + assert!(!content.pinned(&blob, "old").unwrap()); + assert!(content.pinned(&blob, "new").unwrap()); + assert!(content.pinned(&blob, "msg:content-recovery:1").unwrap()); + f.writer() + .content_transaction(&content, |_| Ok(())) + .unwrap(); + let committed = case == "after-commit"; + assert_eq!(content.pinned(&blob, "old").unwrap(), !committed); + assert_eq!(content.pinned(&blob, "new").unwrap(), committed); + assert_eq!(f.setting("domain-row").is_some(), committed); + assert!(!content.pinned(&blob, "msg:content-recovery:1").unwrap()); + assert!(!content.root.join(JOURNAL).exists()); + assert_eq!(content.read_bytes(&blob, 1024).unwrap(), DATA); + } + } +} + +/// Spawned by the parent test, which kills this process with SIGKILL. Ordinary +/// test runs skip it; no unwinding or rollback callback runs at the crash point. +#[test] +#[ignore] +fn crash_child() { + let Ok(root) = std::env::var("MSG_CONTENT_CRASH_ROOT") else { + return; + }; + let root = PathBuf::from(root); + let media = std::env::var("MSG_CONTENT_CRASH_MEDIA").unwrap(); + let case = std::env::var("MSG_CONTENT_CRASH_CASE").unwrap(); + let content = GitContentStore::open(&root.join("store"), ContentOptions::default()).unwrap(); + let mut writer = + SqliteWriter::open_existing(&root.join("metadata.db"), Duration::from_secs(1)).unwrap(); + writer + .content_transaction(&content, |unit| { + unit.unpin(&blob(&media), "old")?; + unit.pin(&blob(&media), "new")?; + unit.metadata() + .set_setting("domain-row", &Json::string("committed"))?; + if case == "before-commit" { + fs::write(root.join("ready"), b"ready").unwrap(); + loop { + thread::park(); + } + } + Ok(()) + }) + .unwrap(); + fs::write(root.join("ready"), b"ready").unwrap(); + loop { + thread::park(); + } +} diff --git a/rust/crates/msg-storage/src/jobs.rs b/rust/crates/msg-storage/src/jobs.rs new file mode 100644 index 00000000..9cb6490a --- /dev/null +++ b/rust/crates/msg-storage/src/jobs.rs @@ -0,0 +1,174 @@ +//! Durable job transitions. Call inside the same fenced write transaction as +//! the protected domain update; a stale callback never owns a newer attempt. +use crate::records::EffectJob; +use msg_core::{Error, Json, Result}; +use msg_identity::models::Timestamp; + +pub const PAGE_SIZE: usize = 64; + +#[derive(Clone)] +pub struct JobCursor { + pub next_at: Timestamp, + pub id: String, +} +impl From<&EffectJob> for JobCursor { + fn from(job: &EffectJob) -> Self { + Self { + next_at: job.next_attempt_at, + id: job.id.clone(), + } + } +} +#[derive(Clone, Copy)] +pub enum JobScan { + Running, + PendingDue(Timestamp), +} + +pub trait JobStore { + fn job(&self, id: &str) -> Result; + fn job_page(&self, scan: JobScan, after: Option<&JobCursor>) -> Result>; + fn save_job_if_attempt(&self, job: &EffectJob, state: &str, attempt: i64) -> Result; + fn job_status(&self, id: &str, status: &Json) -> Result<()>; +} + +pub fn status(code: &str) -> Json { + Json::from_object( + [("code".to_owned(), Json::string(code))] + .into_iter() + .collect(), + ) +} + +pub fn current_attempt( + store: &dyn JobStore, + captured: &EffectJob, + now: Timestamp, +) -> Result> { + let mut current = store.job(&captured.id)?; + if current.state != "running" || current.attempts != captured.attempts { + return Ok(None); + } + if current.lease_until.is_none_or(|until| until <= now) { + current.state = "uncertain".into(); + current.lease_until = None; + if store.save_job_if_attempt(¤t, "running", captured.attempts)? { + store.job_status(¤t.id, &status("expired_execution_lease"))?; + } + return Ok(None); + } + Ok(Some(current)) +} + +pub fn finish( + store: &dyn JobStore, + captured: &EffectJob, + now: Timestamp, + state: &str, + details: &Json, +) -> Result { + if !matches!(state, "done" | "failed" | "uncertain") { + return Err(Error("invalid_job_state")); + } + let Some(mut current) = current_attempt(store, captured, now)? else { + return Ok(false); + }; + current.state = state.into(); + current.lease_until = None; + if !store.save_job_if_attempt(¤t, "running", captured.attempts)? { + return Ok(false); + } + store.job_status(¤t.id, details)?; + Ok(true) +} + +/// Backoff is capped before shifting, including corrupt/historical attempts. +pub fn retry_seconds(attempt: i64) -> i64 { + 30_i64 + .saturating_mul(1_i64 << attempt.saturating_sub(1).clamp(0, 7)) + .min(3600) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::records::decode; + use std::cell::RefCell; + struct Memory { + job: RefCell, + status: RefCell>, + } + impl JobStore for Memory { + fn job(&self, _: &str) -> Result { + Ok(self.job.borrow().clone()) + } + fn job_page(&self, _: JobScan, _: Option<&JobCursor>) -> Result> { + unreachable!() + } + fn save_job_if_attempt(&self, job: &EffectJob, state: &str, attempt: i64) -> Result { + let mut current = self.job.borrow_mut(); + if current.state != state || current.attempts != attempt { + return Ok(false); + } + *current = job.clone(); + Ok(true) + } + fn job_status(&self, _: &str, status: &Json) -> Result<()> { + *self.status.borrow_mut() = Some(status.clone()); + Ok(()) + } + } + fn fixture() -> EffectJob { + decode(&Json::parse(r#"{"id":"job_test","event_id":"evt_test","kind":"mail","dedupe_key":"x","principal":{},"operation":"communication.send","arguments":{},"state":"running","attempts":1,"next_attempt_at":"2026-01-01T00:00:00Z","lease_until":"2026-01-01T00:02:00Z"}"#).unwrap()).unwrap() + } + #[test] + fn an_old_completion_cannot_change_a_new_attempt_or_status() { + let old = fixture(); + let mut newer = old.clone(); + newer.attempts = 2; + let store = Memory { + job: RefCell::new(newer), + status: RefCell::new(Some(status("newer"))), + }; + assert!(!finish(&store, &old, old.next_attempt_at, "done", &status("ok")).unwrap()); + assert_eq!(store.job.borrow().attempts, 2); + assert!(store.status.borrow().as_ref() == Some(&status("newer"))); + } + #[test] + fn an_expired_result_is_uncertain_even_without_a_sweeper() { + let old = fixture(); + let store = Memory { + job: RefCell::new(old.clone()), + status: RefCell::new(None), + }; + assert!(!finish( + &store, + &old, + old.lease_until.unwrap(), + "done", + &status("ok") + ) + .unwrap()); + assert_eq!(store.job.borrow().state, "uncertain"); + assert!(store.status.borrow().as_ref() == Some(&status("expired_execution_lease"))); + } + #[test] + fn acknowledged_result_is_terminal() { + let old = fixture(); + let store = Memory { + job: RefCell::new(old.clone()), + status: RefCell::new(None), + }; + assert!(finish(&store, &old, old.next_attempt_at, "done", &status("sent")).unwrap()); + assert_eq!(store.job.borrow().state, "done"); + assert!(store.job.borrow().lease_until.is_none()); + assert!(!finish(&store, &old, old.next_attempt_at, "failed", &status("late")).unwrap()); + } + #[test] + fn retry_delay_does_not_overflow() { + assert_eq!( + [1, 2, 7, 8, i64::MAX].map(retry_seconds), + [30, 60, 1920, 3600, 3600] + ); + } +} diff --git a/rust/crates/msg-storage/src/lib.rs b/rust/crates/msg-storage/src/lib.rs index 97732060..2fe37148 100644 --- a/rust/crates/msg-storage/src/lib.rs +++ b/rust/crates/msg-storage/src/lib.rs @@ -5,6 +5,7 @@ mod authorization; #[cfg(unix)] pub mod content; +pub mod jobs; pub mod postgres; pub mod records; #[cfg(unix)] diff --git a/rust/crates/msg-storage/src/postgres/identity.rs b/rust/crates/msg-storage/src/postgres/identity.rs new file mode 100644 index 00000000..b836e0e8 --- /dev/null +++ b/rust/crates/msg-storage/src/postgres/identity.rs @@ -0,0 +1,359 @@ +//! Identity mutations on the caller's fenced PostgreSQL transaction. +//! +//! The runtime generation is captured at daemon startup, never from a request. +//! The caller supplies its latched recovery/drill-marker state to `new`. +use super::{PgSession, Row}; +use crate::records::{self, IdentityRecord}; +use msg_core::{b64, unb64, Error, Json, Result}; +use msg_identity::{ + browser::BrowserStore, + models::{decode, Certificate, Credential, Subject, Timestamp, Verifier}, + mutations::{EncryptionKey, IdentityKey, IdentityStore, TokenDelivery}, + sessions::CredentialState, + store::{AuthorityStore, Record, RecoveryDelivery, RuntimeGeneration}, +}; + +pub struct IdentitySession<'a> { + session: &'a PgSession, + generation: &'a mut RuntimeGeneration, + quarantined: bool, +} + +fn require(allowed: bool, code: &'static str) -> Result<()> { + if allowed { + Ok(()) + } else { + Err(Error(code)) + } +} +fn time(value: Timestamp) -> String { + value.0.format("%Y-%m-%dT%H:%M:%S%.6fZ").to_string() +} +fn optional_time(row: &Row, column: usize) -> Result> { + row.optional_text(column)?.map(Timestamp::parse).transpose() +} +fn identity_key(row: &Row) -> Result { + let value = IdentityKey { + key_id: row.text(0)?.into(), + subject: row.text(1)?.into(), + public_key: Verifier(unb64(row.text(2)?, 32)?), + created_at: Timestamp::parse(row.text(3)?)?, + retired_at: optional_time(row, 4)?, + primary: row.integer(5)? != 0, + }; + require( + value.public_key.0.len() == 32 && msg_crypto::key_id(&value.public_key.0) == value.key_id, + "storage_record_mismatch", + )?; + Ok(value) +} +fn encryption_key(row: &Row) -> Result { + let value = EncryptionKey { + key_id: row.text(0)?.into(), + subject: row.text(1)?.into(), + recipient: row.text(2)?.into(), + public_key: Verifier(unb64(row.text(3)?, 32)?), + created_at: Timestamp::parse(row.text(4)?)?, + retired_at: optional_time(row, 5)?, + primary: row.integer(6)? != 0, + }; + let public = msg_crypto::age_recipient::public_from_recipient(&value.recipient)?; + require( + public.as_slice() == value.public_key.0 + && msg_crypto::age_recipient::encryption_key_id(&public) == value.key_id, + "storage_record_mismatch", + )?; + Ok(value) +} +fn delivery(row: &Row) -> Result { + Ok(TokenDelivery { + credential_id: row.text(0)?.into(), + subject: row.text(1)?.into(), + request_id: row.text(2)?.into(), + request_digest: row.text(3)?.into(), + recovery_verifier: row.text(4)?.into(), + recovery_expires_at: Timestamp::parse(row.text(5)?)?, + claimed_at: optional_time(row, 6)?, + consumed_at: optional_time(row, 7)?, + }) +} + +impl<'a> IdentitySession<'a> { + pub fn new( + session: &'a PgSession, + generation: &'a mut RuntimeGeneration, + quarantined: bool, + ) -> Result { + let mut value = Self { + session, + generation, + quarantined, + }; + value.require_live()?; + Ok(value) + } + + fn mutate(&mut self, action: impl FnOnce(&PgSession) -> Result) -> Result { + self.require_live()?; + self.session.require_write()?; + self.session.capture(action(self.session), true) + } +} + +impl AuthorityStore for IdentitySession<'_> { + fn record(&self, kind: Record, id: &str) -> Result { + self.session.record(kind, id) + } + fn setting(&self, key: &str) -> Result> { + self.session.setting(key) + } + fn has_setting(&self, key: &str) -> Result { + self.session.has_setting(key) + } + fn certificate_revoked(&self, id: &str) -> Result { + self.session.certificate_revoked(id) + } + fn request_result( + &self, + subject: Option<&str>, + id: &str, + digest: &str, + ) -> Result> { + self.session.request_result(subject, id, digest) + } + fn credential_state(&self, id: &str) -> Result> { + self.session.credential_state(id) + } + fn custodial_binding(&self, subject: &str) -> Result> { + self.session.custodial_binding(subject) + } + fn recovery_delivery( + &self, + credential: &str, + subject: &str, + ) -> Result> { + self.session.recovery_delivery(credential, subject) + } +} + +impl BrowserStore for IdentitySession<'_> { + fn require_live(&mut self) -> Result<()> { + let result = (|| { + self.session.active(false)?; + self.generation.require_current(self.session)?; + require( + !self.quarantined && !self.session.has_setting("recovery_quarantine")?, + "recovery_quarantined", + ) + })(); + self.session.capture(result, self.session.write) + } + fn save_credential(&mut self, credential: &Credential, auth_version: u64) -> Result<()> { + self.mutate(|tx| tx.save_credential(credential, auth_version)) + } + fn put_state(&mut self, id: &str, kind: &str, expires: Timestamp, body: &Json) -> Result<()> { + self.mutate(|tx| { + let count = tx.execute( + "INSERT INTO credential_states(id,kind,expires,body) VALUES($1,$2,$3,$4) ON CONFLICT(id) DO UPDATE SET expires=excluded.expires,body=excluded.body WHERE credential_states.kind=excluded.kind", + &[id.into(), kind.into(), time(expires).into(), body.canonical()?.into()], + )?; + require(count == 1, "credential_state_conflict") + }) + } + fn replace_state(&mut self, id: &str, body: &Json) -> Result<()> { + self.mutate(|tx| { + let count = tx.execute( + "UPDATE credential_states SET body=$1 WHERE id=$2", + &[body.canonical()?.into(), id.into()], + )?; + require(count == 1, "invalid_grant") + }) + } + fn expire_states(&mut self, now: Timestamp) -> Result<()> { + self.mutate(|tx| { + tx.execute( + "DELETE FROM credential_states WHERE expires<=$1", + &[time(now).into()], + )?; + Ok(()) + }) + } + fn state_count(&self) -> Result { + let count = self + .session + .one("SELECT count(*) FROM credential_states", &[])? + .ok_or(Error("storage_error"))? + .integer(0)?; + u64::try_from(count).map_err(|_| Error("storage_record_mismatch")) + } +} + +impl IdentityStore for IdentitySession<'_> { + fn resolve_identity(&self, reference: &str) -> Result { + if reference.starts_with('/') { + return self.session.resolve(reference, false); + } + require( + !reference.is_empty() + && reference.len() <= 160 + && reference + .bytes() + .all(|byte| byte.is_ascii_alphanumeric() || b"_.:-".contains(&byte)), + "invalid_resource_id", + )?; + Ok(self.session.resource_record(reference)?.id) + } + fn update_subject(&mut self, subject: &Subject, expected: i64) -> Result<()> { + self.mutate(|tx| { + require( + expected + .checked_add(1) + .and_then(|value| u64::try_from(value).ok()) + == Some(subject.auth_version), + "invalid_generation", + )?; + tx.update_identity(IdentityRecord::Subject(subject), expected) + }) + } + fn claim_handle(&mut self, parent: &str, handle: &str, subject: &str) -> Result<()> { + self.mutate(|tx| { + let name = format!("@{handle}"); + let taken = tx.one( + "SELECT EXISTS(SELECT 1 FROM resources WHERE parent=$1 AND name=$2 AND id<>$3) OR EXISTS(SELECT 1 FROM resource_path_aliases WHERE parent_id=$1 AND name=$2 AND resource_id<>$3)", + &[parent.into(), name.into(), subject.into()], + )?.ok_or(Error("storage_error"))?.boolean(0)?; + require(!taken, "handle_unavailable") + }) + } + fn insert_identity_resource(&mut self, resource: &Json) -> Result<()> { + self.mutate(|tx| tx.insert_resource(&records::decode(resource)?)) + } + fn replace_identity_resource(&mut self, resource: &Json, expected: i64) -> Result<()> { + self.mutate(|tx| tx.replace_resource(&records::decode(resource)?, expected)) + } + fn set_identity_setting(&mut self, key: &str, value: &Json) -> Result<()> { + self.mutate(|tx| tx.set_setting(key, value)) + } + fn identity_result(&self, subject: &str, request: &str) -> Result> { + match self.session.one( + "SELECT digest FROM results WHERE subject=$1 AND request_id=$2", + &[subject.into(), request.into()], + )? { + Some(row) => self + .session + .request_result(Some(subject), request, row.text(0)?), + None => Ok(None), + } + } + fn identity_credentials(&self, subject: &str) -> Result> { + self.session + .rows( + "SELECT id,substring(body,1,1048577) FROM credentials WHERE subject=$1 ORDER BY id", + &[subject.into()], + )? + .into_iter() + .map(|row| { + let credential: Credential = decode(&row.json(1)?)?; + require( + credential.subject_id == subject && credential.id == row.text(0)?, + "storage_record_mismatch", + )?; + Ok(credential) + }) + .collect() + } + fn identity_keys(&self, subject: &str) -> Result> { + self.session.rows("SELECT key_id,subject,public_key,created_at,retired_at,is_primary FROM identity_keys WHERE subject=$1 ORDER BY created_at,key_id", &[subject.into()])? + .iter().map(identity_key).collect() + } + fn save_identity_key(&mut self, key: &IdentityKey) -> Result<()> { + self.mutate(|tx| { + require(key.public_key.0.len() == 32 && msg_crypto::key_id(&key.public_key.0) == key.key_id, "invalid_public_key")?; + require(!key.primary || key.retired_at.is_none(), "identity_key_retired")?; + if let Some(row) = tx.one("SELECT key_id,subject,public_key,created_at,retired_at,is_primary FROM identity_keys WHERE key_id=$1", &[key.key_id.as_str().into()])? { + let old = identity_key(&row)?; + require(old.subject == key.subject && old.public_key.0 == key.public_key.0 && old.created_at == key.created_at + && (old.retired_at.is_none() || old.retired_at == key.retired_at), "key_identity_immutable")?; + tx.execute("UPDATE identity_keys SET retired_at=$1,is_primary=$2 WHERE key_id=$3", &[key.retired_at.map(time).into(), i64::from(key.primary).into(), key.key_id.as_str().into()])?; + } else { + tx.subject(&key.subject)?; + tx.execute("INSERT INTO identity_keys(key_id,subject,public_key,created_at,retired_at,is_primary) VALUES($1,$2,$3,$4,$5,$6)", &[ + key.key_id.as_str().into(), key.subject.as_str().into(), b64(&key.public_key.0).into(), time(key.created_at).into(), key.retired_at.map(time).into(), i64::from(key.primary).into(), + ])?; + } + Ok(()) + }) + } + fn encryption_keys(&self, subject: &str) -> Result> { + self.session.rows("SELECT key_id,subject,recipient,public_key,created_at,retired_at,is_primary FROM encryption_subkeys WHERE subject=$1 ORDER BY created_at,key_id", &[subject.into()])? + .iter().map(encryption_key).collect() + } + fn encryption_key_exists(&self, key: &str) -> Result { + self.session + .one( + "SELECT EXISTS(SELECT 1 FROM encryption_subkeys WHERE key_id=$1)", + &[key.into()], + )? + .ok_or(Error("storage_error"))? + .boolean(0) + } + fn save_encryption_key(&mut self, key: &EncryptionKey) -> Result<()> { + self.mutate(|tx| { + let public = msg_crypto::age_recipient::public_from_recipient(&key.recipient)?; + require(public.as_slice() == key.public_key.0 && msg_crypto::age_recipient::encryption_key_id(&public) == key.key_id, "invalid_age_recipient")?; + require(!key.primary || key.retired_at.is_none(), "encryption_key_retired")?; + if let Some(row) = tx.one("SELECT key_id,subject,recipient,public_key,created_at,retired_at,is_primary FROM encryption_subkeys WHERE key_id=$1", &[key.key_id.as_str().into()])? { + let old = encryption_key(&row)?; + require(old.subject == key.subject && old.recipient == key.recipient && old.public_key.0 == key.public_key.0 && old.created_at == key.created_at + && (old.retired_at.is_none() || old.retired_at == key.retired_at), "key_identity_immutable")?; + tx.execute("UPDATE encryption_subkeys SET retired_at=$1,is_primary=$2 WHERE key_id=$3", &[key.retired_at.map(time).into(), i64::from(key.primary).into(), key.key_id.as_str().into()])?; + } else { + tx.subject(&key.subject)?; + tx.execute("INSERT INTO encryption_subkeys(key_id,subject,recipient,public_key,created_at,retired_at,is_primary) VALUES($1,$2,$3,$4,$5,$6,$7)", &[ + key.key_id.as_str().into(), key.subject.as_str().into(), key.recipient.as_str().into(), b64(&key.public_key.0).into(), time(key.created_at).into(), key.retired_at.map(time).into(), i64::from(key.primary).into(), + ])?; + } + Ok(()) + }) + } + fn custodial_migration_pending(&self, subject: &str, now: Timestamp) -> Result { + self.session.one( + "SELECT EXISTS(SELECT 1 FROM custodial_upgrades u LEFT JOIN custodial_vault v ON v.subject=u.subject WHERE u.subject=$1 AND (u.status='pending_rewrap' OR (u.status='pending' AND u.expires_at>$2) OR v.status='decrypt_only'))", + &[subject.into(), time(now).into()], + )?.ok_or(Error("storage_error"))?.boolean(0) + } + fn identity_certificates(&self, subject: &str) -> Result> { + self.session.rows("SELECT id,substring(body,1,1048577) FROM certificates WHERE subject=$1 AND revoked=0 ORDER BY id", &[subject.into()])? + .into_iter().map(|row| { + let cert: Certificate = decode(&row.json(1)?)?; + require(cert.subject_id == subject && cert.resource_id == row.text(0)?, "storage_record_mismatch")?; + Ok(cert) + }).collect() + } + fn register_certificate(&mut self, certificate: &Certificate) -> Result<()> { + self.mutate(|tx| tx.register_certificate(certificate, None)) + } + fn append_identity_audit(&mut self, audit: &Json) -> Result<()> { + self.mutate(|tx| tx.append_audit(&records::decode(audit)?)) + } + fn insert_token_delivery(&mut self, value: &TokenDelivery) -> Result<()> { + self.mutate(|tx| { + require(value.claimed_at.is_none() && value.consumed_at.is_none(), "invalid_token_delivery")?; + require(tx.credential(&value.credential_id)?.subject_id == value.subject, "credential_owner_required")?; + tx.execute("INSERT INTO token_deliveries(credential_id,subject,request_id,request_digest,recovery_verifier,recovery_expires_at,claimed_at,consumed_at) VALUES($1,$2,$3,$4,$5,$6,NULL,NULL)", &[ + value.credential_id.as_str().into(), value.subject.as_str().into(), value.request_id.as_str().into(), value.request_digest.as_str().into(), value.recovery_verifier.as_str().into(), time(value.recovery_expires_at).into(), + ])?; + Ok(()) + }) + } + fn token_delivery(&self, credential: &str) -> Result> { + self.session.one("SELECT credential_id,subject,request_id,request_digest,recovery_verifier,recovery_expires_at,claimed_at,consumed_at FROM token_deliveries WHERE credential_id=$1", &[credential.into()])? + .as_ref().map(delivery).transpose() + } + fn claim_token_delivery(&mut self, credential: &str, now: Timestamp) -> Result { + self.mutate(|tx| Ok(tx.execute("UPDATE token_deliveries SET claimed_at=$1 WHERE credential_id=$2 AND claimed_at IS NULL AND consumed_at IS NULL", &[time(now).into(), credential.into()])? == 1)) + } + fn consume_token_delivery(&mut self, credential: &str, now: Timestamp) -> Result { + self.mutate(|tx| Ok(tx.execute("UPDATE token_deliveries SET consumed_at=$1 WHERE credential_id=$2 AND consumed_at IS NULL", &[time(now).into(), credential.into()])? == 1)) + } +} diff --git a/rust/crates/msg-storage/src/postgres/jobs.rs b/rust/crates/msg-storage/src/postgres/jobs.rs new file mode 100644 index 00000000..cb02176b --- /dev/null +++ b/rust/crates/msg-storage/src/postgres/jobs.rs @@ -0,0 +1,78 @@ +use super::PgSession; +use crate::{ + jobs::{JobCursor, JobScan, JobStore, PAGE_SIZE}, + records::{encode, EffectJob}, +}; +use msg_core::{Error, Json, Result}; +use msg_identity::{ + models::Timestamp, store::AuthorityStore, worker_authority::WorkerAuthorityStore, +}; + +fn time(value: Timestamp) -> String { + value.0.format("%Y-%m-%dT%H:%M:%S%.6fZ").to_string() +} + +impl JobStore for PgSession { + fn job(&self, id: &str) -> Result { + PgSession::job(self, id) + } + + fn job_page(&self, scan: JobScan, after: Option<&JobCursor>) -> Result> { + let cursor = after + .map(|c| time(c.next_at)) + .unwrap_or_else(|| "0001-01-01T00:00:00.000000Z".into()); + let id = after.map(|c| c.id.as_str()).unwrap_or(""); + // Paging IDs first keeps individual 1 MiB jobs below the aggregate SQL + // limit; each full row is validated by the existing typed reader. + let rows = match scan { + JobScan::Running => self.rows("SELECT id,kind,dedupe FROM jobs WHERE state='running' AND (next_at,id)>($1,$2) ORDER BY next_at,id LIMIT $3", &[cursor.into(), id.into(), (PAGE_SIZE as i64).into()])?, + JobScan::PendingDue(now) => self.rows("SELECT id,kind,dedupe FROM jobs WHERE state='pending' AND next_at<=$1 AND (next_at,id)>($2,$3) ORDER BY next_at,id LIMIT $4", &[time(now).into(), cursor.into(), id.into(), (PAGE_SIZE as i64).into()])?, + }; + rows.iter() + .map(|row| { + let job = PgSession::job(self, row.text(0)?)?; + if job.kind != row.text(1)? || job.dedupe_key != row.text(2)? { + return Err(Error("storage_record_mismatch")); + } + Ok(job) + }) + .collect() + } + + fn save_job_if_attempt(&self, job: &EffectJob, state: &str, attempt: i64) -> Result { + self.require_write()?; + let result = (|| { + if job.attempts < 0 + || attempt < 0 + || !matches!( + job.state.as_str(), + "pending" | "running" | "done" | "failed" | "uncertain" + ) + { + return Err(Error("invalid_job_state")); + } + Ok(self.execute("UPDATE jobs SET state=$1,next_at=$2,body=$3 WHERE id=$4 AND state=$5 AND (body::jsonb->>'attempts')::bigint=$6", &[job.state.as_str().into(), time(job.next_attempt_at).into(), encode(job)?.canonical()?.into(), job.id.as_str().into(), state.into(), attempt.into()])? == 1) + })(); + self.capture(result, true) + } + + fn job_status(&self, id: &str, status: &Json) -> Result<()> { + self.set_setting(&format!("job_status:{id}"), status) + } +} + +impl WorkerAuthorityStore for PgSession { + fn authority(&self) -> &dyn AuthorityStore { + self + } + fn worker_snapshot(&self, id: &str, attempt: i64, now: Timestamp) -> Result { + let job = self.job(id)?; + if job.state != "running" + || job.attempts != attempt + || job.lease_until.is_none_or(|until| until <= now) + { + return Err(Error("job_lease_lost")); + } + Ok(job.principal_snapshot().clone()) + } +} diff --git a/rust/crates/msg-storage/src/postgres/mod.rs b/rust/crates/msg-storage/src/postgres/mod.rs index de878511..22db44f7 100644 --- a/rust/crates/msg-storage/src/postgres/mod.rs +++ b/rust/crates/msg-storage/src/postgres/mod.rs @@ -4,8 +4,11 @@ //! session advisory lock as Python and retain it through all compensations. //! Transaction pooling is unsupported. No schema is created or upgraded here. mod authority; +mod identity; +mod jobs; mod query; mod records; +pub use identity::IdentitySession; use msg_core::{Error, Result}; use postgres::{fallible_iterator::FallibleIterator, types::ToSql, Client, Config}; use postgres_native_tls::MakeTlsConnector; diff --git a/rust/crates/msg-storage/src/records.rs b/rust/crates/msg-storage/src/records.rs index cb38d897..8fae3684 100644 --- a/rust/crates/msg-storage/src/records.rs +++ b/rust/crates/msg-storage/src/records.rs @@ -378,6 +378,12 @@ pub struct JobDraft { pub arguments: BTreeMap, } impl EffectJob { + /// Durable evidence only. It must be revalidated through worker_authority + /// before use; this accessor never creates an authenticated principal. + pub fn principal_snapshot(&self) -> &Json { + &self.principal + } + pub fn new( principal: &msg_identity::authentication::Principal, draft: JobDraft, diff --git a/rust/crates/msg-worker/Cargo.toml b/rust/crates/msg-worker/Cargo.toml new file mode 100644 index 00000000..5df0c22f --- /dev/null +++ b/rust/crates/msg-worker/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "msg-worker" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +publish.workspace = true + +[dependencies] +msg-core = { path = "../msg-core" } +msg-identity = { path = "../msg-identity" } +msg-storage = { path = "../msg-storage" } +chrono.workspace = true +serde.workspace = true +serde_json.workspace = true +base64.workspace = true +native-tls.workspace = true +hmac.workspace = true +sha2.workspace = true +aes-gcm.workspace = true +zeroize.workspace = true +libc.workspace = true + +[lints] +workspace = true diff --git a/rust/crates/msg-worker/src/delivery.rs b/rust/crates/msg-worker/src/delivery.rs new file mode 100644 index 00000000..6377f30d --- /dev/null +++ b/rust/crates/msg-worker/src/delivery.rs @@ -0,0 +1,576 @@ +//! Reconstruct outbound messages only after current authority and endpoint +//! generations have been checked. Queued body text never grants read access. +use crate::{ + mail::{MailMessage, SmtpSender}, + outbound, AttemptContext, EffectOutcome, EffectResult, +}; +use aes_gcm::{ + aead::{Aead, Payload}, + Aes256Gcm, KeyInit, Nonce, +}; +use msg_core::{unb64, Error, Json, Result}; +use msg_identity::{ + authentication::{Entry, Principal}, + authorization::{AccessRequirement, AuthorizationService, Check}, + models::{ResourceRef, Timestamp}, + store::{truthy, AuthorityStore}, + worker_authority::WorkerAuthority, +}; +use msg_storage::{ + postgres::PgSession, + records::{decode, encode, EffectJob, EmailSettings, Event, ResourceState}, +}; +use std::collections::{BTreeMap, BTreeSet}; +use zeroize::Zeroizing; + +pub struct DeliveryService { + service_url: String, + origin: String, + hubs: BTreeSet, + smtp: SmtpSender, + authority: WorkerAuthority, + authorizer: AuthorizationService, + vault_key: Zeroizing<[u8; 32]>, +} +impl DeliveryService { + pub fn new( + service_url: String, + hubs: BTreeSet, + smtp: SmtpSender, + authority: WorkerAuthority, + authorizer: AuthorizationService, + vault_key: Zeroizing<[u8; 32]>, + ) -> Result { + let origin = service_origin(&service_url)?; + for hub in &hubs { + outbound::Endpoint::parse(hub)?; + } + Ok(Self { + service_url, + origin, + hubs, + smtp, + authority, + authorizer, + vault_key, + }) + } + pub fn websub(&mut self, context: &mut AttemptContext<'_, '_>) -> EffectResult { + let (hub, topic) = context.read(|_, job, _| { + let hub = argument(job, "hub")?.as_str()?; + let topic = argument(job, "topic")?.as_str()?; + require(self.hubs.contains(hub), "websub_hub_removed")?; + require( + topic == format!("{}/rss.xml", self.service_url), + "invalid_websub_topic", + )?; + Ok((hub.to_owned(), topic.to_owned())) + })?; + outbound::websub(&hub, &topic) + } + /// Ordinary account-email jobs. Order notifications use projected_mail and + /// the market's current order/delivery verifier instead of queued text. + pub fn mail(&mut self, context: &mut AttemptContext<'_, '_>) -> EffectResult { + self.projected_mail(context, |tx, _, job, now| { + require( + !flag(&job.arguments, "order_notification")?, + "order_projection_required", + )?; + let subject = argument(job, "recipient_subject")?.as_str()?; + let recipient = argument(job, "recipient")?.as_str()?; + let row = tx + .one( + "SELECT substring(body,1,1048577) FROM emails WHERE subject=$1", + &[subject.into()], + )? + .ok_or(Error("email_not_set"))?; + let email: EmailSettings = decode(&row.json(0)?)?; + require( + email.subject_id == subject && email.address.as_deref() == Some(recipient), + "email_changed", + )?; + if flag(&job.arguments, "verification")? { + let challenge = tx + .one( + "SELECT expires FROM email_challenges WHERE subject=$1", + &[subject.into()], + )? + .ok_or(Error("email_challenge_expired"))?; + require( + Timestamp::parse(challenge.text(0)?)? > now, + "email_challenge_expired", + )?; + } else { + require( + email.verified_at.is_some() && email.enabled_events.contains(&job.operation), + "notification_disabled", + )?; + } + Ok(MailMessage { + recipient: recipient.into(), + subject: argument(job, "subject")?.as_str()?.into(), + text: argument(job, "text")?.as_str()?.into(), + delivery_id: job.id.clone(), + }) + }) + } + /// The supplied projector must reconstruct a market/order notification from + /// current immutable order facts. It runs inside this authority snapshot. + pub fn projected_mail( + &mut self, + context: &mut AttemptContext<'_, '_>, + projection: impl FnOnce(&PgSession, &Principal, &EffectJob, Timestamp) -> Result, + ) -> EffectResult { + require(self.smtp.enabled(), "mail_disabled")?; + let message = context.read(|tx, job, now| { + let principal = + self.authority + .current(&job.id, job.attempts, &mut self.authorizer, tx, now)?; + let message = projection(tx, &principal, job, now)?; + require(message.delivery_id == job.id, "invalid_delivery_result")?; + Ok(message) + })?; + self.smtp.send(&message) + } + pub fn webhook(&mut self, context: &mut AttemptContext<'_, '_>) -> EffectResult { + let job = context.job().clone(); + let (url,secret,mut envelope)=context.read(|tx,job,now| { + let principal=self.authority.current(&job.id,job.attempts,&mut self.authorizer,tx,now)?; + let recipient=argument(job,"recipient_subject")?.as_str()?; + require(!tx.subject(recipient)?.local_only,"local_only")?; + let endpoint=tx.one("SELECT url,nonce,ciphertext,enabled,generation FROM webhook_endpoints WHERE subject=$1",&[recipient.into()])?.ok_or(Error("webhook_disabled"))?; + let generation=integer(argument(job,"endpoint_generation")?)?; + require(endpoint.integer(3)?==1 && endpoint.integer(4)?==generation,"webhook_disabled")?; + let mut envelope=BTreeMap::from([ + ("event_id".into(),Json::string(&job.event_id)), + ("delivery_id".into(),Json::string(&job.id)), + ("subject_id".into(),Json::string(recipient)), + ]); + match job.operation.as_str() { + "communication.send"=>{ + let message=tx.one("SELECT sender,recipient,event_id FROM messages WHERE id=$1",&[argument(job,"message_id")?.as_str()?.into()])?.ok_or(Error("webhook_event_missing"))?; + require(message.optional_text(0)?==principal.subject() && message.optional_text(1)?==Some(recipient) && message.optional_text(2)?==Some(&job.event_id),"webhook_event_missing")?; + envelope.insert("type".into(),Json::string("inbox.reference")); + } + "communication.webhook_subscribe"=>self.domain_event(tx,job,&principal,recipient,generation,now,&mut envelope)?, + "communication.game_webhook_subscribe"=>self.game_event(tx,job,&principal,recipient,generation,now,&mut envelope)?, + _=>return Err(Error("invalid_webhook_event")), + } + let url=endpoint.text(0)?.to_owned(); outbound::Endpoint::parse(&url)?; + let nonce=unb64(endpoint.text(1)?,12)?; let ciphertext=unb64(endpoint.text(2)?,96)?; + require(nonce.len()==12,"invalid_webhook_secret")?; + let aad=format!("webhook-v1\0{recipient}"); + let cipher=Aes256Gcm::new_from_slice(self.vault_key.as_ref()).map_err(|_|Error("invalid_webhook_secret"))?; + let secret=Zeroizing::new(cipher.decrypt(Nonce::from_slice(&nonce),Payload {msg:&ciphertext,aad:aad.as_bytes()}).map_err(|_|Error("invalid_webhook_secret"))?); + require((32..=64).contains(&secret.len()),"invalid_webhook_secret")?; + Ok((url,secret,envelope)) + })?; + let timestamp = context.now()?.0.timestamp().to_string(); + envelope.insert("timestamp".into(), Json::string(×tamp)); + let body = Json::from_object(envelope).canonical()?.into_bytes(); + outbound::webhook(&url, &secret, &job.event_id, &job.id, ×tamp, &body) + } + + #[allow(clippy::too_many_arguments)] + fn domain_event( + &mut self, + tx: &PgSession, + job: &EffectJob, + principal: &Principal, + recipient: &str, + endpoint_generation: i64, + now: Timestamp, + envelope: &mut BTreeMap, + ) -> Result<()> { + require( + principal.subject() == Some(recipient) + && principal.actor() == Some(recipient) + && principal.method() == "signature", + "webhook_subscription_owner", + )?; + let rid = argument(job, "resource_id")?.as_str()?; + let scope_id = argument(job, "scope_id")?.as_str()?; + let category = argument(job, "category")?.as_str()?; + let subscription = tx + .setting(&format!("webhook_subscription:{recipient}:{scope_id}"))? + .ok_or(Error("webhook_subscription_disabled"))?; + let subscription = subscription.as_object()?; + require( + flag(subscription, "enabled")? + && integer(field(subscription, "generation")?)? + == integer(argument(job, "subscription_generation")?)? + && integer(field(subscription, "endpoint_generation")?)? == endpoint_generation + && contains(field(subscription, "events")?, category)? + && field( + field(subscription, "principal")?.as_object()?, + "credential_id", + )? + .as_str()? + == principal.credential_id().unwrap_or(""), + "webhook_subscription_disabled", + )?; + let event = tx + .one( + "SELECT substring(body,1,1048577) FROM events WHERE id=$1", + &[job.event_id.as_str().into()], + )? + .ok_or(Error("webhook_event_missing"))?; + let event: Event = decode(&event.json(0)?)?; + let expected = match event.event_type.as_str() { + "content.post_create" | "discussion.reply" => "resource.created", + "content.post_edit" => "resource.updated", + _ => return Err(Error("invalid_webhook_event")), + }; + require( + event.id == job.event_id + && category == expected + && event.resources.iter().any(|r| r.id == rid), + "invalid_webhook_event", + )?; + let scope = tx.resource_record(scope_id)?; + let resource = tx.resource_record(rid)?; + require( + scope.owner == recipient + && scope.state == ResourceState::Active + && resource.state == ResourceState::Active + && (scope.id == resource.id || resource.parent.as_deref() == Some(scope_id)), + "webhook_scope_changed", + )?; + require( + self.authorizer.has( + principal, + "webhook.domain", + "communication.webhook_subscribe@1", + scope_id, + tx, + now, + )?, + "capability_required", + )?; + self.authorizer.require( + principal, + Entry::Worker, + now, + &[ + AccessRequirement { + resource_id: scope_id.into(), + operation: "communication.webhook_subscribe@1".into(), + check: Check::Read, + }, + AccessRequirement { + resource_id: rid.into(), + operation: "communication.webhook_subscribe@1".into(), + check: Check::Read, + }, + ], + None, + tx, + )?; + envelope.insert("resource_id".into(), Json::string(rid)); + envelope.insert("type".into(), Json::string(category)); + envelope.insert( + "event".into(), + event_envelope(&self.origin, &event, rid, category)?, + ); + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn game_event( + &mut self, + tx: &PgSession, + job: &EffectJob, + principal: &Principal, + recipient: &str, + endpoint_generation: i64, + now: Timestamp, + envelope: &mut BTreeMap, + ) -> Result<()> { + require( + job.arguments + .keys() + .map(String::as_str) + .collect::>() + == BTreeSet::from([ + "contract_version", + "recipient_subject", + "subscription_generation", + "endpoint_generation", + "event", + ]) + && integer(argument(job, "contract_version")?)? == 1, + "invalid_game_event", + )?; + let event = argument(job, "event")?; + bounded_game_event(event)?; + require( + field(event.as_object()?, "id")?.as_str()? == job.event_id, + "invalid_game_event", + )?; + let subscription_generation = integer(argument(job, "subscription_generation")?)?; + require( + (1..=9_007_199_254_740_991).contains(&subscription_generation) + && (1..=9_007_199_254_740_991).contains(&endpoint_generation), + "invalid_game_subscription", + )?; + let owner = tx.resource_record(recipient)?; + require( + owner.resource_type == "user" + && owner.owner == recipient + && owner.state == ResourceState::Active, + "game_webhook_owner", + )?; + let subscription = tx + .setting(&format!("game_webhook:{recipient}"))? + .ok_or(Error("game_webhook_subscription_disabled"))?; + let subscription = subscription.as_object()?; + require( + field(subscription, "enabled")?.as_bool()? + && integer(field(subscription, "generation")?)? == subscription_generation + && integer(field(subscription, "endpoint_generation")?)? == endpoint_generation, + "game_webhook_subscription_disabled", + )?; + require( + principal.actor() == Some(recipient) + && principal.subject() == Some(recipient) + && principal.method() == "signature" + && field(subscription, "principal")? == job.principal_snapshot(), + "game_webhook_owner", + )?; + let category = field(event.as_object()?, "type")?.as_str()?; + require( + contains(field(subscription, "events")?, category)?, + "game_event_not_subscribed", + )?; + self.authorizer.require_base( + principal, + "communication.game_webhook_subscribe@1", + recipient, + tx, + now, + )?; + envelope.insert("type".into(), Json::string(category)); + envelope.insert("event".into(), event.clone()); + Ok(()) + } +} + +fn require(value: bool, code: &'static str) -> Result<()> { + if value { + Ok(()) + } else { + Err(Error(code)) + } +} +fn field<'a>(object: &'a BTreeMap, key: &str) -> Result<&'a Json> { + object.get(key).ok_or(Error("invalid_job_arguments")) +} +fn argument<'a>(job: &'a EffectJob, key: &str) -> Result<&'a Json> { + field(&job.arguments, key) +} +fn integer(value: &Json) -> Result { + value + .as_integer()? + .parse() + .map_err(|_| Error("invalid_job_arguments")) +} +fn flag(object: &BTreeMap, key: &str) -> Result { + object + .get(key) + .map(truthy) + .transpose() + .map(|v| v.unwrap_or(false)) +} +fn contains(value: &Json, expected: &str) -> Result { + Ok(value + .as_array()? + .iter() + .any(|v| v.as_str().ok() == Some(expected))) +} +fn identifier(value: &str) -> bool { + !value.is_empty() + && value.len() <= 160 + && value + .bytes() + .all(|c| c.is_ascii_alphanumeric() || matches!(c, b'_' | b'.' | b':' | b'-')) +} +fn resource_address(origin: &str, reference: &ResourceRef) -> Result { + require( + identifier(&reference.id) && reference.revision.as_deref().is_none_or(identifier), + "invalid_resource_id", + )?; + let path = match &reference.revision { + Some(revision) => format!("/_r/{}/rev/{revision}", reference.id), + None => format!("/_r/{}/json", reference.id), + }; + encode( + &serde_json::json!({"service":origin,"ref":reference,"path":path,"url":format!("{origin}{path}")}), + ) +} +fn event_envelope(origin: &str, event: &Event, rid: &str, category: &str) -> Result { + require(identifier(&event.id), "invalid_webhook_event")?; + let resources = event + .resources + .iter() + .filter(|r| r.id == rid) + .map(|r| resource_address(origin, r)) + .collect::>>()?; + let path = format!("/_e/{}", event.id); + encode( + &serde_json::json!({"version":1,"path":path,"url":format!("{origin}{path}"),"id":event.id,"source":origin,"type":category,"operation":event.event_type,"time":event.time,"request_id":event.request_id,"actor":null,"subject":null,"resources":resources}), + ) +} +fn service_origin(service: &str) -> Result { + require( + !service.is_empty() + && service.bytes().all(|c| c > 32 && c != 127) + && !service.contains(['\\', '?', '#', '@']), + "invalid_resource_address", + )?; + let (scheme, rest) = service + .split_once("://") + .ok_or(Error("invalid_resource_address"))?; + let scheme = scheme.to_ascii_lowercase(); + require( + matches!(scheme.as_str(), "https" | "http"), + "invalid_resource_address", + )?; + let authority = rest.strip_suffix('/').unwrap_or(rest); + require( + !authority.is_empty() && !authority.contains('/'), + "invalid_resource_address", + )?; + let (host, port) = if authority.starts_with('[') { + let end = authority + .find(']') + .ok_or(Error("invalid_resource_address"))?; + let host = &authority[..=end]; + require( + host[1..host.len() - 1] + .parse::() + .is_ok(), + "invalid_resource_address", + )?; + let suffix = &authority[end + 1..]; + ( + host, + if suffix.is_empty() { + None + } else { + Some( + suffix + .strip_prefix(':') + .ok_or(Error("invalid_resource_address"))?, + ) + }, + ) + } else if let Some((host, port)) = authority.rsplit_once(':') { + (host, Some(port)) + } else { + (authority, None) + }; + require(!host.is_empty(), "invalid_resource_address")?; + let port = port + .map(|p| { + p.parse::() + .ok() + .filter(|v| *v > 0) + .ok_or(Error("invalid_resource_address")) + }) + .transpose()?; + let default = if scheme == "https" { 443 } else { 80 }; + let suffix = port + .filter(|p| *p != default) + .map(|p| format!(":{p}")) + .unwrap_or_default(); + Ok(format!("{scheme}://{}{suffix}", host.to_ascii_lowercase())) +} +fn bounded_game_event(event: &Json) -> Result<()> { + let fields = event.as_object()?; + require( + ["id", "type", "ship_id", "time_ms"] + .iter() + .all(|k| fields.contains_key(*k)) + && fields.keys().all(|k| { + matches!( + k.as_str(), + "id" | "type" | "ship_id" | "time_ms" | "region" | "score" | "collected" | "hp" + ) + }), + "invalid_game_event", + )?; + let id = field(fields, "id")?.as_str()?; + let ship = field(fields, "ship_id")?.as_str()?; + let kind = field(fields, "type")?.as_str()?; + require( + id.strip_prefix("ge_").is_some_and(|s| { + !s.is_empty() + && s.len() <= 156 + && s.bytes() + .all(|c| c.is_ascii_alphanumeric() || matches!(c, b'_' | b'-')) + }) && ship.strip_prefix("ship_").is_some_and(|s| { + s.len() == 24 + && s.bytes() + .all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)) + }) && matches!( + kind, + "game.joined" | "game.left" | "game.region" | "game.collect" | "game.hit" + ), + "invalid_game_event", + )?; + for key in ["time_ms", "score", "collected"] { + if let Some(value) = fields.get(key) { + require( + (0..=9_007_199_254_740_991).contains(&integer(value)?), + "invalid_game_event", + )?; + } + } + if let Some(value) = fields.get("region") { + require((0..19).contains(&integer(value)?), "invalid_game_event")?; + } + if let Some(value) = fields.get("hp") { + let hp = value + .canonical()? + .parse::() + .map_err(|_| Error("invalid_game_event"))?; + require( + hp.is_finite() && (0.0..=100.0).contains(&hp), + "invalid_game_event", + )?; + } + require(event.canonical()?.len() <= 2048, "game_event_too_large") +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn game_projection_rejects_extra_fields_boolean_numbers_and_foreign_content() { + let event=Json::parse(r#"{"id":"ge_1","type":"game.hit","ship_id":"ship_0123456789abcdef01234567","time_ms":1,"hp":50.0}"#).unwrap(); + assert!(bounded_game_event(&event).is_ok()); + let mut fields = event.clone().into_object().unwrap(); + fields.insert("private_world".into(), Json::string("secret")); + assert!(bounded_game_event(&Json::from_object(fields)).is_err()); + let mut fields = event.into_object().unwrap(); + fields.insert("time_ms".into(), Json::parse("true").unwrap()); + assert!(bounded_game_event(&Json::from_object(fields)).is_err()); + } + #[test] + fn public_event_envelope_has_only_authorized_references() { + let event:Event=decode(&Json::parse(r#"{"id":"e_1","type":"content.post_edit","time":"2026-01-01T00:00:00Z","request_id":"q_1","actor":"u_secret","subject":"u_secret","resources":[{"id":"r_allowed","revision":"rev_1"},{"id":"r_secret","revision":null}],"data":{"secret":"private"}}"#).unwrap()).unwrap(); + let envelope = event_envelope( + "https://example.com", + &event, + "r_allowed", + "resource.updated", + ) + .unwrap() + .canonical() + .unwrap(); + assert!(!envelope.contains("u_secret")); + assert!(!envelope.contains("r_secret")); + assert!(!envelope.contains("private")); + assert!(envelope.contains("r_allowed/rev/rev_1")); + } +} diff --git a/rust/crates/msg-worker/src/lib.rs b/rust/crates/msg-worker/src/lib.rs new file mode 100644 index 00000000..d1e38147 --- /dev/null +++ b/rust/crates/msg-worker/src/lib.rs @@ -0,0 +1,643 @@ +//! Native durable worker. Database leases do not make an external side effect +//! exactly once; expired non-idempotent work is deliberately left uncertain. +pub mod delivery; +pub mod mail; +pub mod outbound; + +use chrono::{DateTime, Duration as TimeDelta}; +use msg_core::{Error, Json, Result}; +use msg_identity::{ + models::{ResourceRef, Timestamp}, + store::{AuthorityStore, RuntimeGeneration}, +}; +use msg_storage::{ + jobs::{self, JobCursor, JobScan, JobStore, PAGE_SIZE}, + postgres::{PgFailure, PgSession, PgStore}, + records::{decode, EffectJob}, +}; +use std::{ + collections::BTreeMap, + fs, + path::{Path, PathBuf}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, + time::{Duration, Instant, SystemTime, UNIX_EPOCH}, +}; + +pub type WorkerResult = std::result::Result; +pub type EffectResult = std::result::Result; +type Clock = Arc Result + Send + Sync>; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum EffectKind { + Tool, + Mail, + MarketMail, + MarketEmailVerify, + WebSub, + Webhook, + GitPush, + Maintenance, + ResourceGc, +} +impl EffectKind { + pub const ALL: [Self; 9] = [ + Self::Tool, + Self::Mail, + Self::MarketMail, + Self::MarketEmailVerify, + Self::WebSub, + Self::Webhook, + Self::GitPush, + Self::Maintenance, + Self::ResourceGc, + ]; + pub fn parse(name: &str) -> Result { + Ok(match name { + "tool" => Self::Tool, + "mail" => Self::Mail, + "market_mail" => Self::MarketMail, + "market_email_verify" => Self::MarketEmailVerify, + "websub" => Self::WebSub, + "webhook" => Self::Webhook, + "git.push" => Self::GitPush, + "maintenance" => Self::Maintenance, + "gc.resource" => Self::ResourceGc, + _ => return Err(Error("unknown_effect_kind")), + }) + } + pub fn as_str(self) -> &'static str { + match self { + Self::Tool => "tool", + Self::Mail => "mail", + Self::MarketMail => "market_mail", + Self::MarketEmailVerify => "market_email_verify", + Self::WebSub => "websub", + Self::Webhook => "webhook", + Self::GitPush => "git.push", + Self::Maintenance => "maintenance", + Self::ResourceGc => "gc.resource", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Maintenance { + CleanupExpired, + CollectGarbage, + DeliverDueTodos, + RebuildSearch, +} +impl Maintenance { + pub const SCHEDULED: [Self; 3] = [ + Self::CleanupExpired, + Self::CollectGarbage, + Self::DeliverDueTodos, + ]; + pub fn parse(action: &str) -> Result { + Ok(match action { + "cleanup_expired" => Self::CleanupExpired, + "collect_garbage" => Self::CollectGarbage, + "deliver_due_todos" => Self::DeliverDueTodos, + "rebuild_search" => Self::RebuildSearch, + _ => return Err(Error("invalid_maintenance_action")), + }) + } + pub fn as_str(self) -> &'static str { + match self { + Self::CleanupExpired => "cleanup_expired", + Self::CollectGarbage => "collect_garbage", + Self::DeliverDueTodos => "deliver_due_todos", + Self::RebuildSearch => "rebuild_search", + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct EffectFailure { + pub error: Error, + pub retryable: bool, +} +impl From for EffectFailure { + fn from(error: Error) -> Self { + Self { + error, + retryable: false, + } + } +} +impl From for EffectFailure { + fn from(error: PgFailure) -> Self { + match error { + PgFailure::Uncertain { .. } => Error("external_uncertain").into(), + PgFailure::Rejected { error, .. } => error.into(), + } + } +} + +pub enum EffectOutcome { + Done(Json), + Failed(&'static str), + Uncertain(&'static str), + Retry { + code: &'static str, + exhausted: &'static str, + }, + /// The handler atomically published its result and job state through + /// AttemptContext::publish. Returning this alone is rejected by the engine. + Published, +} + +/// Domain handlers are mandatory; this interface has no success/no-op defaults. +/// The daemon must provide the installed operation registry and real handlers. +pub trait EffectExecutor { + fn operation(&self, name: &str, version: u32) -> Result<()>; + fn tool_concurrency(&mut self, tx: &PgSession, tool: &ResourceRef) -> EffectResult; + fn resolve_due(&mut self, context: &mut RuntimeContext<'_>) -> WorkerResult; + fn resolve_cases(&mut self, context: &mut RuntimeContext<'_>) -> WorkerResult; + fn maintenance( + &mut self, + action: Maintenance, + context: &mut RuntimeContext<'_>, + ) -> WorkerResult<()>; + fn execute( + &mut self, + kind: EffectKind, + context: &mut AttemptContext<'_, '_>, + ) -> EffectResult; +} + +pub struct WorkerConfig { + pub lease_seconds: i64, + pub orders_enabled: bool, + pub recovery_drill_marker: Option, + pub recovery_quarantine_marker: Option, +} +impl Default for WorkerConfig { + fn default() -> Self { + Self { + lease_seconds: 120, + orders_enabled: false, + recovery_drill_marker: None, + recovery_quarantine_marker: None, + } + } +} +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Tick { + Processed, + Idle, + Stopped, + RecoveryDrill, + Quarantined, +} +impl Tick { + pub fn processed(self) -> bool { + self == Self::Processed + } +} + +pub struct Worker { + store: PgStore, + executor: E, + config: WorkerConfig, + runtime: RuntimeGeneration, + clock: Clock, + last_maintenance: Option, +} + +fn utc_now() -> Result { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|_| Error("invalid_clock"))?; + let seconds = i64::try_from(now.as_secs()).map_err(|_| Error("invalid_clock"))?; + DateTime::from_timestamp(seconds, now.subsec_micros() * 1000) + .map(Timestamp) + .ok_or(Error("invalid_clock")) +} +fn marker(path: Option<&Path>) -> Result { + let Some(path) = path else { + return Ok(false); + }; + match fs::symlink_metadata(path) { + Ok(_) => Ok(true), // A symlink is a quarantine marker too. + Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(false), + Err(_) => Err(Error("recovery_layout_error")), + } +} + +/// Transactions must enter through this runtime fence. A stale runtime is +/// permanently invalid, even if a recovery setting later returns to its value. +pub struct RuntimeContext<'a> { + store: &'a PgStore, + runtime: &'a mut RuntimeGeneration, + config: &'a WorkerConfig, + clock: &'a Clock, + stopping: &'a AtomicBool, +} +impl RuntimeContext<'_> { + pub fn now(&self) -> Result { + (self.clock)() + } + pub fn stopping(&self) -> bool { + self.stopping.load(Ordering::Acquire) + } + pub fn transaction( + &mut self, + write: bool, + body: impl FnOnce(&PgSession) -> Result, + ) -> WorkerResult { + if marker(self.config.recovery_drill_marker.as_deref())? { + return Err(Error("recovery_drill_active").into()); + } + if marker(self.config.recovery_quarantine_marker.as_deref())? { + return Err(Error("recovery_quarantined").into()); + } + self.store.transaction(write, |tx| { + self.runtime.require_current(tx)?; + if tx.has_setting("recovery_quarantine")? { + return Err(Error("recovery_quarantined")); + } + body(tx) + }) + } +} + +pub struct AttemptContext<'a, 'b> { + runtime: &'a mut RuntimeContext<'b>, + job: &'a EffectJob, + published: bool, +} +impl AttemptContext<'_, '_> { + pub fn job(&self) -> &EffectJob { + self.job + } + pub fn now(&self) -> Result { + self.runtime.now() + } + pub fn stopping(&self) -> bool { + self.runtime.stopping() + } + /// Read and revalidate all effect-specific authority in this snapshot. + pub fn read( + &mut self, + body: impl FnOnce(&PgSession, &EffectJob, Timestamp) -> Result, + ) -> WorkerResult { + let now = self.now()?; + self.runtime.transaction(false, |tx| { + let current = tx.job(&self.job.id)?; + if current.state != "running" + || current.attempts != self.job.attempts + || current.lease_until.is_none_or(|until| until <= now) + { + return Err(Error("job_lease_lost")); + } + body(tx, ¤t, now) + }) + } + /// Reauthorize and publish a domain result in the same fenced transaction + /// as the final job state. External I/O must precede, never run inside, this. + pub fn publish( + &mut self, + details: &Json, + body: impl FnOnce(&PgSession, &mut EffectJob, Timestamp) -> Result, + ) -> WorkerResult> { + let now = self.now()?; + let value = self.runtime.transaction(true, |tx| { + let Some(mut current) = jobs::current_attempt(tx, self.job, now)? else { + return Ok(None); + }; + let output = body(tx, &mut current, now)?; + current.state = "done".into(); + current.lease_until = None; + if !tx.save_job_if_attempt(¤t, "running", self.job.attempts)? { + return Err(Error("job_lease_lost")); + } + tx.job_status(¤t.id, details)?; + Ok(Some(output)) + })?; + self.published = true; + Ok(value) + } +} + +impl Worker { + pub fn new(store: PgStore, executor: E, config: WorkerConfig) -> WorkerResult { + Self::with_clock(store, executor, config, Arc::new(utc_now)) + } + pub fn with_clock( + store: PgStore, + executor: E, + config: WorkerConfig, + clock: Clock, + ) -> WorkerResult { + if !(1..=3600).contains(&config.lease_seconds) { + return Err(Error("invalid_job_lease").into()); + } + let runtime = store.transaction(false, |tx| RuntimeGeneration::capture(tx))?; + Ok(Self { + store, + executor, + config, + runtime, + clock, + last_maintenance: None, + }) + } + pub fn run_once(&mut self, stopping: &AtomicBool) -> WorkerResult { + if stopping.load(Ordering::Acquire) { + return Ok(Tick::Stopped); + } + // Check generation even while paused; it must latch stale permanently. + self.store + .transaction(false, |tx| self.runtime.require_current(tx))?; + if marker(self.config.recovery_drill_marker.as_deref())? { + return Ok(Tick::RecoveryDrill); + } + if marker(self.config.recovery_quarantine_marker.as_deref())? + || self + .store + .transaction(false, |tx| tx.has_setting("recovery_quarantine"))? + { + return Ok(Tick::Quarantined); + } + let mut context = RuntimeContext { + store: &self.store, + runtime: &mut self.runtime, + config: &self.config, + clock: &self.clock, + stopping, + }; + if self + .last_maintenance + .is_none_or(|last| last.elapsed() >= Duration::from_secs(60)) + { + for action in Maintenance::SCHEDULED { + if context.stopping() { + return Ok(Tick::Stopped); + } + self.executor.maintenance(action, &mut context)?; + } + self.last_maintenance = Some(Instant::now()); + } + if context.stopping() { + return Ok(Tick::Stopped); + } + if self.config.orders_enabled { + if self.executor.resolve_due(&mut context)? { + return Ok(Tick::Processed); + } + if context.stopping() { + return Ok(Tick::Stopped); + } + if self.executor.resolve_cases(&mut context)? { + return Ok(Tick::Processed); + } + } + if context.stopping() { + return Ok(Tick::Stopped); + } + let now = context.now()?; + let claimed = context.transaction(true, |tx| { + claim(tx, now, self.config.lease_seconds, stopping, |tool| { + self.executor.tool_concurrency(tx, tool) + }) + })?; + let job = match claimed { + Claim::None => { + return Ok(if stopping.load(Ordering::Acquire) { + Tick::Stopped + } else { + Tick::Idle + }) + } + Claim::Swept => return Ok(Tick::Processed), + Claim::Job(job) => job, + }; + // Stop after claiming leaves the lease intact; no new external call is + // started during shutdown and a successor cannot blindly resend it. + if context.stopping() { + return Ok(Tick::Stopped); + } + let mut attempt = AttemptContext { + runtime: &mut context, + job: &job, + published: false, + }; + let result = (|| { + let version = contract_version(&job)?; + self.executor.operation(&job.operation, version)?; + self.executor + .execute(EffectKind::parse(&job.kind)?, &mut attempt) + })(); + let published = attempt.published; + let result = match result { + Ok(EffectOutcome::Published) if published => return Ok(Tick::Processed), + Ok(EffectOutcome::Published) => EffectOutcome::Uncertain("external_uncertain"), + Ok(outcome) => outcome, + Err(failure) + if matches!( + job.kind.as_str(), + "mail" | "market_mail" | "market_email_verify" + ) && failure.retryable + && failure.error.0 == "mail_connection_failed" => + { + EffectOutcome::Retry { + code: "mail_connection_failed", + exhausted: "mail_connection_failed", + } + } + Err(failure) if matches!(failure.error.0, "external_uncertain" | "job_lease_lost") => { + EffectOutcome::Uncertain(failure.error.0) + } + Err(failure) => EffectOutcome::Failed(failure.error.0), + }; + let now = context.now()?; + context.transaction(true, |tx| complete(tx, &job, now, result))?; + Ok(Tick::Processed) + } +} + +fn contract_version(job: &EffectJob) -> Result { + let version = match job.arguments.get("contract_version") { + None => 1, + Some(value) => value + .as_integer() + .ok() + .and_then(|s| s.parse::().ok()) + .filter(|v| *v > 0) + .ok_or(Error("invalid_job_contract_version"))?, + }; + Ok(version) +} +enum Claim { + None, + Swept, + Job(EffectJob), +} +fn advance(now: Timestamp, seconds: i64) -> Result { + now.0 + .checked_add_signed(TimeDelta::seconds(seconds)) + .map(Timestamp) + .ok_or(Error("invalid_datetime")) +} +fn claim( + store: &dyn JobStore, + now: Timestamp, + lease: i64, + stopping: &AtomicBool, + mut concurrency: impl FnMut(&ResourceRef) -> EffectResult, +) -> Result { + if stopping.load(Ordering::Acquire) { + return Ok(Claim::None); + } + let mut running = BTreeMap::::new(); + let mut cursor = None; + loop { + let page = store.job_page(JobScan::Running, cursor.as_ref())?; + let last = page.last().map(JobCursor::from); + let count = page.len(); + for mut job in page { + if stopping.load(Ordering::Acquire) { + return Ok(Claim::None); + } + if job.lease_until.is_none_or(|until| until <= now) { + let mut code = "expired_execution_lease"; + job.state = "uncertain".into(); + job.lease_until = None; + if job.kind == "websub" { + if job.attempts >= 8 { + job.state = "failed".into(); + code = "websub_attempts_exhausted"; + } else { + job.state = "pending".into(); + job.next_attempt_at = advance(now, jobs::retry_seconds(job.attempts))?; + } + } + if store.save_job_if_attempt(&job, "running", job.attempts)? { + store.job_status(&job.id, &jobs::status(code))?; + } + return Ok(Claim::Swept); + } + if job.kind == "tool" { + let tool: ResourceRef = decode( + job.arguments + .get("tool") + .ok_or(Error("invalid_job_arguments"))?, + )?; + let count = running.entry(tool.id).or_default(); + *count = count.checked_add(1).ok_or(Error("worker_work_limit"))?; + } + } + if count < PAGE_SIZE { + break; + } + cursor = last; + } + let mut limits = BTreeMap::new(); + cursor = None; + loop { + let page = store.job_page(JobScan::PendingDue(now), cursor.as_ref())?; + let last = page.last().map(JobCursor::from); + let count = page.len(); + for mut job in page { + if stopping.load(Ordering::Acquire) { + return Ok(Claim::None); + } + if job.kind == "tool" { + let tool: ResourceRef = decode( + job.arguments + .get("tool") + .ok_or(Error("invalid_job_arguments"))?, + )?; + let key = (tool.id.clone(), tool.revision.clone()); + if !limits.contains_key(&key) { + match concurrency(&tool) { + Ok(limit) if (1..=32).contains(&limit) => { + limits.insert(key.clone(), limit); + } + result => { + let failure = result + .err() + .unwrap_or_else(|| Error("invalid_tool_concurrency").into()); + if failure.retryable { + return Err(failure.error); + } + job.state = "failed".into(); + job.lease_until = None; + if store.save_job_if_attempt(&job, "pending", job.attempts)? { + store.job_status(&job.id, &jobs::status(failure.error.0))?; + } + return Ok(Claim::Swept); + } + } + } + if running.get(&tool.id).copied().unwrap_or(0) >= limits[&key] { + continue; + } + } + if stopping.load(Ordering::Acquire) { + return Ok(Claim::None); + } + let previous = job.attempts; + job.state = "running".into(); + job.attempts = previous.checked_add(1).ok_or(Error("invalid_job_state"))?; + job.lease_until = Some(advance(now, lease)?); + if store.save_job_if_attempt(&job, "pending", previous)? { + return Ok(Claim::Job(job)); + } + } + if count < PAGE_SIZE { + break; + } + cursor = last; + } + Ok(Claim::None) +} +fn complete( + store: &dyn JobStore, + job: &EffectJob, + now: Timestamp, + outcome: EffectOutcome, +) -> Result<()> { + match outcome { + EffectOutcome::Done(status) => { + jobs::finish(store, job, now, "done", &status)?; + } + EffectOutcome::Failed(code) => { + jobs::finish(store, job, now, "failed", &jobs::status(code))?; + } + EffectOutcome::Uncertain(code) => { + jobs::finish(store, job, now, "uncertain", &jobs::status(code))?; + } + EffectOutcome::Retry { code, exhausted } => { + let Some(mut current) = jobs::current_attempt(store, job, now)? else { + return Ok(()); + }; + let exhausted_attempts = current.attempts >= 8; + current.state = if exhausted_attempts { + "failed" + } else { + "pending" + } + .into(); + current.lease_until = None; + if !exhausted_attempts { + current.next_attempt_at = advance(now, jobs::retry_seconds(current.attempts))?; + } + if store.save_job_if_attempt(¤t, "running", job.attempts)? { + store.job_status( + ¤t.id, + &jobs::status(if exhausted_attempts { exhausted } else { code }), + )?; + } + } + EffectOutcome::Published => return Err(Error("invalid_job_state")), + } + Ok(()) +} + +#[cfg(test)] +mod tests; diff --git a/rust/crates/msg-worker/src/mail.rs b/rust/crates/msg-worker/src/mail.rs new file mode 100644 index 00000000..493896d9 --- /dev/null +++ b/rust/crates/msg-worker/src/mail.rs @@ -0,0 +1,496 @@ +//! TLS-only SMTP with an explicit uncertainty boundary. Any error after MAIL +//! starts is uncertain, and a failure closing an acknowledged DATA is ignored. +use crate::{ + outbound::{connect, remaining, resolve}, + EffectFailure, EffectOutcome, EffectResult, +}; +use base64::{engine::general_purpose::STANDARD, Engine}; +use msg_core::{Error, Result}; +use msg_storage::jobs::status; +use serde::Deserialize; +use std::{ + collections::BTreeSet, + fs::{File, OpenOptions}, + io::{Read, Write}, + net::TcpStream, + path::PathBuf, + time::{Duration, Instant}, +}; +use zeroize::{Zeroize, Zeroizing}; + +#[derive(Clone, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct MailConfig { + pub enabled: bool, + pub host: String, + pub port: u16, + pub tls: String, + pub sender: String, + pub credential_file: Option, +} +pub struct MailMessage { + pub recipient: String, + pub subject: String, + pub text: String, + pub delivery_id: String, +} +pub struct SmtpSender { + config: Option, +} +impl SmtpSender { + pub fn new(config: Option) -> Self { + Self { config } + } + pub fn enabled(&self) -> bool { + self.config.as_ref().is_some_and(|c| c.enabled) + } + pub fn send(&self, message: &MailMessage) -> EffectResult { + let config = self + .config + .as_ref() + .filter(|c| c.enabled) + .ok_or(Error("mail_disabled"))?; + // All header and recipient validation precedes connecting/secret reads. + validate_address(&message.recipient)?; + let (sender, from) = sender(&config.sender)?; + let raw = render(message, &from)?; + if !matches!(config.tls.as_str(), "tls" | "starttls") + || config.host.is_empty() + || config.host.chars().any(char::is_whitespace) + || config.port == 0 + { + return Err(Error("mail_tls_required").into()); + } + let deadline = Instant::now() + Duration::from_secs(20); + let mut sending = false; + let result = (|| { + let tls = + native_tls::TlsConnector::new().map_err(|_| Error("mail_connection_failed"))?; + let addresses = resolve(&config.host, config.port, deadline)?; + let socket = connect(&addresses, deadline)?; + let stream = if config.tls == "tls" { + MailStream::Tls( + tls.connect(&config.host, socket) + .map_err(|_| Error("mail_connection_failed"))?, + ) + } else { + MailStream::Plain(socket) + }; + let mut connection = Connection { stream, deadline }; + expect(connection.reply()?.0, 220)?; + let mut capabilities = connection.ehlo()?; + if config.tls == "starttls" { + if !capabilities.contains("STARTTLS") { + return Err(Error("mail_connection_failed")); + } + expect(connection.command("STARTTLS")?.0, 220)?; + connection.stream = match connection.stream { + MailStream::Plain(socket) => MailStream::Tls( + tls.connect(&config.host, socket) + .map_err(|_| Error("mail_connection_failed"))?, + ), + MailStream::Tls(_) => return Err(Error("mail_connection_failed")), + }; + capabilities = connection.ehlo()?; + } + if let Some(path) = &config.credential_file { + let credentials = credentials(path)?; + if capabilities.contains("PLAIN") { + let mut bytes = Zeroizing::new(Vec::new()); + bytes.push(0); + bytes.extend_from_slice(credentials.username.as_bytes()); + bytes.push(0); + bytes.extend_from_slice(credentials.password.as_bytes()); + let encoded = Zeroizing::new(STANDARD.encode(&*bytes)); + let command = Zeroizing::new(format!("AUTH PLAIN {}", encoded.as_str())); + let mut response = connection.command(&command)?; + if response.0 == 334 { + response = connection.command(&encoded)?; + } + expect(response.0, 235)?; + } else if capabilities.contains("LOGIN") { + expect(connection.command("AUTH LOGIN")?.0, 334)?; + let username = Zeroizing::new(STANDARD.encode(credentials.username.as_bytes())); + let password = Zeroizing::new(STANDARD.encode(credentials.password.as_bytes())); + expect(connection.command(&username)?.0, 334)?; + expect(connection.command(&password)?.0, 235)?; + } else { + return Err(Error("mail_connection_failed")); + } + } + let utf8 = !sender.is_ascii() || !message.recipient.is_ascii(); + if utf8 && !capabilities.contains("SMTPUTF8") { + return Err(Error("mail_connection_failed")); + } + // This intentionally starts before MAIL, like smtplib.send_message: + // a later failure is never automatically retried as an unsent mail. + sending = true; + let options = if utf8 { " SMTPUTF8 BODY=8BITMIME" } else { "" }; + expect( + connection + .command(&format!("MAIL FROM:<{sender}>{options}"))? + .0, + 250, + )?; + let rcpt = connection + .command(&format!("RCPT TO:<{}>", message.recipient))? + .0; + if !matches!(rcpt, 250 | 251) { + return Err(Error("mail_recipient_refused")); + } + expect(connection.command("DATA")?.0, 354)?; + connection.set_timeout()?; + connection + .stream + .write_all(&raw) + .and_then(|_| connection.stream.write_all(b".\r\n")) + .and_then(|_| connection.stream.flush()) + .map_err(|_| Error("mail_connection_failed"))?; + expect(connection.reply()?.0, 250)?; + // Drop closes locally; QUIT cannot turn accepted DATA into a retry. + Ok(()) + })(); + match result { + Ok(()) => Ok(EffectOutcome::Done(status("sent"))), + Err(_) if sending => Ok(EffectOutcome::Uncertain("external_uncertain")), + Err(_) => Err(EffectFailure { + error: Error("mail_connection_failed"), + retryable: true, + }), + } + } +} + +#[derive(Deserialize)] +#[serde(deny_unknown_fields)] +struct Credentials { + username: String, + password: String, +} +impl Drop for Credentials { + fn drop(&mut self) { + self.username.zeroize(); + self.password.zeroize(); + } +} +fn credentials(path: &std::path::Path) -> Result { + let mut options = OpenOptions::new(); + options.read(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + options.custom_flags(libc::O_NOFOLLOW | libc::O_NONBLOCK); + } + let file = options + .open(path) + .map_err(|_| Error("invalid_smtp_credentials"))?; + let metadata = file + .metadata() + .map_err(|_| Error("invalid_smtp_credentials"))?; + if !metadata.is_file() || metadata.len() > 65_536 { + return Err(Error("invalid_smtp_credentials")); + } + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + if metadata.permissions().mode() & 0o077 != 0 { + return Err(Error("invalid_smtp_credentials")); + } + } + read_credentials(file) +} +fn read_credentials(file: File) -> Result { + let mut raw = Zeroizing::new(Vec::new()); + file.take(65_537) + .read_to_end(&mut raw) + .map_err(|_| Error("invalid_smtp_credentials"))?; + if raw.len() > 65_536 { + return Err(Error("invalid_smtp_credentials")); + } + let value: Credentials = + serde_json::from_slice(&raw).map_err(|_| Error("invalid_smtp_credentials"))?; + if value.username.contains('\0') || value.password.contains('\0') { + return Err(Error("invalid_smtp_credentials")); + } + Ok(value) +} + +enum MailStream { + Plain(TcpStream), + Tls(native_tls::TlsStream), +} +impl Read for MailStream { + fn read(&mut self, bytes: &mut [u8]) -> std::io::Result { + match self { + Self::Plain(v) => v.read(bytes), + Self::Tls(v) => v.read(bytes), + } + } +} +impl Write for MailStream { + fn write(&mut self, bytes: &[u8]) -> std::io::Result { + match self { + Self::Plain(v) => v.write(bytes), + Self::Tls(v) => v.write(bytes), + } + } + fn flush(&mut self) -> std::io::Result<()> { + match self { + Self::Plain(v) => v.flush(), + Self::Tls(v) => v.flush(), + } + } +} +struct Connection { + stream: MailStream, + deadline: Instant, +} +impl Connection { + fn set_timeout(&self) -> Result<()> { + let socket = match &self.stream { + MailStream::Plain(v) => v, + MailStream::Tls(v) => v.get_ref(), + }; + socket + .set_read_timeout(Some(remaining(self.deadline)?)) + .and_then(|_| { + socket.set_write_timeout(Some( + remaining(self.deadline).map_err(std::io::Error::other)?, + )) + }) + .map_err(|_| Error("mail_connection_failed")) + } + fn command(&mut self, value: &str) -> Result<(u16, Vec)> { + if value.len() > 65_536 || value.contains(['\r', '\n']) { + return Err(Error("mail_connection_failed")); + } + self.set_timeout()?; + self.stream + .write_all(value.as_bytes()) + .and_then(|_| self.stream.write_all(b"\r\n")) + .and_then(|_| self.stream.flush()) + .map_err(|_| Error("mail_connection_failed"))?; + self.reply() + } + fn reply(&mut self) -> Result<(u16, Vec)> { + let mut lines = Vec::new(); + let mut code = None; + let mut total = 0; + loop { + let mut line = Vec::new(); + while !line.ends_with(b"\r\n") { + if line.len() >= 4096 || total >= 65_536 { + return Err(Error("mail_connection_failed")); + } + self.set_timeout()?; + let mut byte = [0]; + self.stream + .read_exact(&mut byte) + .map_err(|_| Error("mail_connection_failed"))?; + line.push(byte[0]); + total += 1; + } + if line.len() < 6 + || !line[..3].iter().all(u8::is_ascii_digit) + || !matches!(line[3], b' ' | b'-') + { + return Err(Error("mail_connection_failed")); + } + let current = ((line[0] - b'0') as u16) * 100 + + ((line[1] - b'0') as u16) * 10 + + (line[2] - b'0') as u16; + if code.is_some_and(|c| c != current) { + return Err(Error("mail_connection_failed")); + } + code = Some(current); + lines.push(String::from_utf8_lossy(&line[4..line.len() - 2]).into_owned()); + if line[3] == b' ' { + return Ok((current, lines)); + } + } + } + fn ehlo(&mut self) -> Result> { + let (code, lines) = self.command("EHLO msgd.local")?; + expect(code, 250)?; + Ok(lines + .iter() + .flat_map(|line| line.split_ascii_whitespace()) + .map(str::to_ascii_uppercase) + .collect()) + } +} +fn expect(actual: u16, expected: u16) -> Result<()> { + if actual == expected { + Ok(()) + } else { + Err(Error("mail_connection_failed")) + } +} + +pub fn validate_address(address: &str) -> Result<()> { + if address.is_empty() + || address.len() > 254 + || address.chars().any(|c| c.is_whitespace() || c.is_control()) + { + return Err(Error("invalid_email")); + } + let (local, domain) = address.rsplit_once('@').ok_or(Error("invalid_email"))?; + if local.is_empty() + || domain.is_empty() + || !domain.contains('.') + || domain.contains('@') + || domain.starts_with('.') + || domain.ends_with('.') + { + return Err(Error("invalid_email")); + } + let atom = |s: &str| { + !s.is_empty() + && s.chars().all(|c| { + !c.is_control() + && !c.is_whitespace() + && (!c.is_ascii() + || c.is_ascii_alphanumeric() + || "!#$%&'*+-/=?^_`{|}~".contains(c)) + }) + }; + let local_ok = local.split('.').all(atom); + // Quoted local parts are kept byte-for-byte; only the minimal form accepted + // by the existing exact addr-spec contract is accepted here. + let quoted = local + .strip_prefix('"') + .and_then(|s| s.strip_suffix('"')) + .is_some_and(|s| { + !s.is_empty() + && !s.contains(['"', '\\', '@']) + && s.chars() + .any(|c| !c.is_ascii_alphanumeric() && !".!#$%&'*+-/=?^_`{|}~".contains(c)) + }); + if !(local_ok || quoted) + || !domain.split('.').all(|part| { + !part.is_empty() + && part.chars().all(|c| c.is_alphanumeric() || c == '-') + && !part.starts_with('-') + && !part.ends_with('-') + }) + { + return Err(Error("invalid_email")); + } + Ok(()) +} +fn sender(value: &str) -> Result<(String, String)> { + if value.len() > 1024 || value.chars().any(char::is_control) { + return Err(Error("invalid_mail_sender")); + } + if let Some((name, rest)) = value.rsplit_once('<') { + let address = rest.strip_suffix('>').ok_or(Error("invalid_mail_sender"))?; + validate_address(address)?; + if name.contains(['<', '>']) { + return Err(Error("invalid_mail_sender")); + } + let name = name.trim(); + Ok(( + address.into(), + format!("{} <{address}>", encoded_header(name)), + )) + } else { + validate_address(value)?; + Ok((value.into(), value.into())) + } +} +fn encoded_header(value: &str) -> String { + if value.is_ascii() && value.len() <= 72 { + return value.into(); + } + let mut words = Vec::new(); + let mut chunk = String::new(); + for c in value.chars() { + if chunk.len() + c.len_utf8() > 42 { + words.push(format!("=?UTF-8?B?{}?=", STANDARD.encode(chunk.as_bytes()))); + chunk.clear(); + } + chunk.push(c); + } + if !chunk.is_empty() { + words.push(format!("=?UTF-8?B?{}?=", STANDARD.encode(chunk.as_bytes()))); + } + words.join("\r\n ") +} +fn render(message: &MailMessage, sender: &str) -> Result> { + if message.subject.len() > 8192 + || message.subject.chars().any(char::is_control) + || message.text.len() > msg_core::MAX_BYTES + || message.delivery_id.is_empty() + || message.delivery_id.len() > 160 + || !message + .delivery_id + .bytes() + .all(|c| c.is_ascii_alphanumeric() || matches!(c, b'_' | b'-')) + { + return Err(Error("invalid_mail_message")); + } + let mut message_bytes = format!("From: {sender}\r\nTo: {}\r\nSubject: {}\r\nMessage-ID: <{}@msgd.local>\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: base64\r\n\r\n",message.recipient,encoded_header(&message.subject),message.delivery_id).into_bytes(); + let mut text = message.text.replace("\r\n", "\n").replace('\r', "\n"); + if !text.ends_with('\n') { + text.push('\n'); + } + let body = STANDARD.encode(text.replace('\n', "\r\n").as_bytes()); + for line in body.as_bytes().chunks(76) { + message_bytes.extend_from_slice(line); + message_bytes.extend_from_slice(b"\r\n"); + } + Ok(message_bytes) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn envelope_rejects_header_injection_and_multiple_targets() { + for address in [ + "a@example.com\r\nBcc:b@example.com", + "a@example.com,b@example.com", + "Name", + "a@@example.com", + "a..b@example.com", + "a@example", + "a@-example.com", + ] { + assert!(validate_address(address).is_err(), "{address}"); + } + for address in ["a+b@example.com", "中文@example.com", "a.b@example.test"] { + assert!(validate_address(address).is_ok(), "{address}"); + } + } + #[test] + fn rendered_body_cannot_end_smtp_data_or_add_headers() { + let message = MailMessage { + recipient: "a@example.com".into(), + subject: "中文主题".into(), + text: "hello\r\n.\r\nBcc: attacker".into(), + delivery_id: "job_stable".into(), + }; + let raw = String::from_utf8(render(&message, "sender@example.com").unwrap()).unwrap(); + assert!(!raw.contains("\r\n.\r\n")); + assert!(!raw.contains("Bcc: attacker")); + assert!(raw.contains("Message-ID: ")); + assert!(raw.contains("=?UTF-8?B?")); + } + #[test] + fn disabled_mail_never_connects_or_reads_credentials() { + let sender = SmtpSender::new(None); + let message = MailMessage { + recipient: "invalid".into(), + subject: String::new(), + text: String::new(), + delivery_id: String::new(), + }; + assert!(matches!( + sender.send(&message), + Err(EffectFailure { + error: Error("mail_disabled"), + .. + }) + )); + } +} diff --git a/rust/crates/msg-worker/src/outbound.rs b/rust/crates/msg-worker/src/outbound.rs new file mode 100644 index 00000000..f31f74d8 --- /dev/null +++ b/rust/crates/msg-worker/src/outbound.rs @@ -0,0 +1,464 @@ +//! Fixed-target HTTPS delivery. DNS is resolved once, every returned address is +//! checked, and the connection uses that exact address with TLS name validation. +//! No redirects, proxies, cookies, environment credentials, or response bodies. +use crate::{EffectOutcome, EffectResult}; +use hmac::{Hmac, Mac}; +use msg_core::{Error, Result}; +use msg_storage::jobs::status; +use sha2::Sha256; +use std::{ + io::{Read, Write}, + net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, TcpStream, ToSocketAddrs}, + sync::{ + mpsc::{self, SyncSender}, + OnceLock, + }, + thread, + time::{Duration, Instant}, +}; + +#[derive(Clone)] +pub struct Endpoint { + host: String, + target: String, + host_header: String, +} +impl Endpoint { + pub fn parse(url: &str) -> Result { + if url.is_empty() + || url.len() > 2048 + || !url.bytes().all(|c| (33..127).contains(&c)) + || url.contains(['\\', '#']) + { + return Err(Error("invalid_webhook_url")); + } + let (scheme, rest) = url.split_once("://").ok_or(Error("invalid_webhook_url"))?; + if !scheme.eq_ignore_ascii_case("https") { + return Err(Error("invalid_webhook_url")); + } + let (authority, path) = match rest.find(['/', '?']) { + Some(i) => (&rest[..i], &rest[i..]), + None => (rest, ""), + }; + if authority.is_empty() || authority.contains('@') { + return Err(Error("invalid_webhook_url")); + } + let (host, port) = if let Some(value) = authority.strip_prefix('[') { + let (host, suffix) = value.split_once(']').ok_or(Error("invalid_webhook_url"))?; + if host.parse::().is_err() { + return Err(Error("invalid_webhook_url")); + } + let port = if suffix.is_empty() { + None + } else { + Some( + suffix + .strip_prefix(':') + .ok_or(Error("invalid_webhook_url"))?, + ) + }; + (host, port) + } else if let Some((host, port)) = authority.rsplit_once(':') { + if host.contains(':') { + return Err(Error("invalid_webhook_url")); + } + (host, Some(port)) + } else { + (authority, None) + }; + if let Some(port) = port { + if port.is_empty() + || !port.bytes().all(|c| c.is_ascii_digit()) + || port.parse::() != Ok(443) + { + return Err(Error("webhook_port_forbidden")); + } + } + let host = normalized_host(host)?; + if let Ok(ip) = host.parse::() { + require_public(ip)?; + } + let host_header = if host.contains(':') { + format!("[{host}]") + } else { + host.clone() + }; + let target = if path.is_empty() { + "/".into() + } else if path.starts_with('?') { + format!("/{path}") + } else { + path.into() + }; + Ok(Self { + host, + target, + host_header, + }) + } + pub fn host(&self) -> &str { + &self.host + } +} + +fn normalized_host(host: &str) -> Result { + if host.is_empty() || host.len() > 253 || host.contains('%') || !host.is_ascii() { + return Err(Error("invalid_host")); + } + if let Ok(ip) = host.parse::() { + return Ok(ip.to_string()); + } + let host = host.trim_end_matches('.').to_ascii_lowercase(); + if !host.split('.').all(|label| { + !label.is_empty() + && label.len() <= 63 + && label.as_bytes()[0].is_ascii_alphanumeric() + && label.as_bytes()[label.len() - 1].is_ascii_alphanumeric() + && label + .bytes() + .all(|c| c.is_ascii_alphanumeric() || c == b'-') + }) { + return Err(Error("invalid_host")); + } + Ok(host) +} + +fn in_v4(ip: Ipv4Addr, network: u32, bits: u32) -> bool { + u32::from(ip) >> (32 - bits) == network >> (32 - bits) +} +fn in_v6(ip: Ipv6Addr, network: &str, bits: u32) -> bool { + // Source-owned constants below, never untrusted input. + let Ok(network) = network.parse::() else { + return false; + }; + u128::from(ip) >> (128 - bits) == u128::from(network) >> (128 - bits) +} +pub fn require_public(ip: IpAddr) -> Result<()> { + let allowed = match ip { + IpAddr::V4(ip) => { + let exception = matches!(ip.octets(), [192, 0, 0, 9] | [192, 0, 0, 10]); + exception + || ![ + (0x00000000, 8), + (0x0a000000, 8), + (0x64400000, 10), + (0x7f000000, 8), + (0xa9fe0000, 16), + (0xac100000, 12), + (0xc0000000, 24), + (0xc0000200, 24), + (0xc0a80000, 16), + (0xc6120000, 15), + (0xc6336400, 24), + (0xcb007100, 24), + (0xe0000000, 4), + (0xf0000000, 4), + ] + .iter() + .any(|&(network, bits)| in_v4(ip, network, bits)) + } + IpAddr::V6(ip) => { + if let Some(mapped) = ip.to_ipv4_mapped() { + return require_public(IpAddr::V4(mapped)); + } + let exception = [ + ("2001:1::1", 128), + ("2001:1::2", 128), + ("2001:3::", 32), + ("2001:4:112::", 48), + ("2001:20::", 28), + ("2001:30::", 28), + ] + .iter() + .any(|&(n, b)| in_v6(ip, n, b)); + exception + || ![ + ("::", 128), + ("::1", 128), + ("64:ff9b:1::", 48), + ("100::", 64), + ("2001::", 23), + ("2001:db8::", 32), + ("2002::", 16), + ("3fff::", 20), + ("fc00::", 7), + ("fe80::", 10), + ("fec0::", 10), + ("ff00::", 8), + ] + .iter() + .any(|&(n, b)| in_v6(ip, n, b)) + } + }; + if allowed { + Ok(()) + } else { + Err(Error("private_target_forbidden")) + } +} + +type DnsRequest = (String, u16, SyncSender>>); +static RESOLVER: OnceLock>> = OnceLock::new(); +pub(crate) fn remaining(deadline: Instant) -> Result { + deadline + .checked_duration_since(Instant::now()) + .filter(|d| !d.is_zero()) + .ok_or(Error("external_uncertain")) +} +pub(crate) fn resolve(host: &str, port: u16, deadline: Instant) -> Result> { + if let Ok(ip) = host.parse::() { + return Ok(vec![SocketAddr::new(ip, port)]); + } + let sender = RESOLVER + .get_or_init(|| { + let (sender, receiver) = mpsc::sync_channel::(1); + thread::Builder::new() + .name("msg-worker-dns".into()) + .spawn(move || { + while let Ok((host, port, response)) = receiver.recv() { + let result = (host.as_str(), port) + .to_socket_addrs() + .map(|v| v.collect::>()) + .map_err(|_| Error("external_uncertain")); + let _ = response.send(result); + } + }) + .ok() + .map(|_| sender) + }) + .as_ref() + .ok_or(Error("external_uncertain"))?; + let (response, receiver) = mpsc::sync_channel(1); + sender + .try_send((host.to_owned(), port, response)) + .map_err(|_| Error("external_uncertain"))?; + let addresses = receiver + .recv_timeout(remaining(deadline)?) + .map_err(|_| Error("external_uncertain"))??; + if addresses.is_empty() { + return Err(Error("external_uncertain")); + } + Ok(addresses) +} +pub(crate) fn connect(addresses: &[SocketAddr], deadline: Instant) -> Result { + for address in addresses { + if let Ok(stream) = TcpStream::connect_timeout(address, remaining(deadline)?) { + stream + .set_read_timeout(Some(remaining(deadline)?)) + .map_err(|_| Error("external_uncertain"))?; + stream + .set_write_timeout(Some(remaining(deadline)?)) + .map_err(|_| Error("external_uncertain"))?; + return Ok(stream); + } + } + Err(Error("external_uncertain")) +} + +fn post( + endpoint: &Endpoint, + content_type: &str, + body: &[u8], + headers: &[(&str, &str)], +) -> Result { + if body.len() > msg_core::MAX_BYTES { + return Err(Error("request_too_large")); + } + let deadline = Instant::now() + Duration::from_secs(8); + let addresses = resolve(&endpoint.host, 443, deadline)?; + // Reject a mixed public/private DNS answer before opening any connection. + for address in &addresses { + require_public(address.ip())?; + } + let socket = connect(&addresses, deadline)?; + let tls = native_tls::TlsConnector::new().map_err(|_| Error("external_uncertain"))?; + let mut stream = tls + .connect(&endpoint.host, socket) + .map_err(|_| Error("external_uncertain"))?; + let mut head = format!("POST {} HTTP/1.1\r\nHost: {}\r\nContent-Type: {}\r\nContent-Length: {}\r\nConnection: close\r\n", endpoint.target, endpoint.host_header, content_type, body.len()); + for (name, value) in headers { + if name.is_empty() + || !name.bytes().all(|c| c.is_ascii_alphanumeric() || c == b'-') + || value.len() > 4096 + || !value.bytes().all(|c| (32..127).contains(&c)) + { + return Err(Error("invalid_webhook_header")); + } + head.push_str(name); + head.push_str(": "); + head.push_str(value); + head.push_str("\r\n"); + } + head.push_str("\r\n"); + stream + .get_ref() + .set_write_timeout(Some(remaining(deadline)?)) + .map_err(|_| Error("external_uncertain"))?; + stream + .write_all(head.as_bytes()) + .and_then(|_| stream.write_all(body)) + .and_then(|_| stream.flush()) + .map_err(|_| Error("external_uncertain"))?; + for _ in 0..4 { + let mut response = Vec::new(); + while !response.ends_with(b"\r\n\r\n") { + if response.len() >= 16_384 { + return Err(Error("external_uncertain")); + } + stream + .get_ref() + .set_read_timeout(Some(remaining(deadline)?)) + .map_err(|_| Error("external_uncertain"))?; + let mut byte = [0]; + stream + .read_exact(&mut byte) + .map_err(|_| Error("external_uncertain"))?; + response.push(byte[0]); + } + let line = response + .split(|&c| c == b'\r') + .next() + .ok_or(Error("external_uncertain"))?; + let mut fields = line.split(|&c| c == b' '); + if !matches!(fields.next(), Some(b"HTTP/1.0" | b"HTTP/1.1")) { + return Err(Error("external_uncertain")); + } + let raw = fields.next().ok_or(Error("external_uncertain"))?; + if raw.len() != 3 || !raw.iter().all(u8::is_ascii_digit) { + return Err(Error("external_uncertain")); + } + let status = + ((raw[0] - b'0') as u16) * 100 + ((raw[1] - b'0') as u16) * 10 + (raw[2] - b'0') as u16; + if status >= 200 { + return Ok(status); + } + } + Err(Error("external_uncertain")) +} + +fn form(value: &str) -> String { + let mut output = String::new(); + for byte in value.bytes() { + match byte { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + output.push(char::from(byte)) + } + b' ' => output.push('+'), + _ => output.push_str(&format!("%{byte:02X}")), + } + } + output +} +pub fn websub(hub: &str, topic: &str) -> EffectResult { + let endpoint = Endpoint::parse(hub)?; + let body = format!("hub.mode=publish&hub.url={}", form(topic)); + match post( + &endpoint, + "application/x-www-form-urlencoded", + body.as_bytes(), + &[], + ) { + Ok(200..=299) => Ok(EffectOutcome::Done(status("websub_delivered"))), + Ok(408 | 429 | 500..=999) | Err(Error("external_uncertain")) => Ok(EffectOutcome::Retry { + code: "websub_retry_scheduled", + exhausted: "websub_attempts_exhausted", + }), + Ok(_) => Ok(EffectOutcome::Failed("websub_failed")), + Err(error) => Err(error.into()), + } +} + +pub fn webhook( + url: &str, + secret: &[u8], + event: &str, + delivery: &str, + timestamp: &str, + body: &[u8], +) -> EffectResult { + let endpoint = Endpoint::parse(url)?; + if !(32..=64).contains(&secret.len()) { + return Err(Error("invalid_webhook_secret").into()); + } + let mut signer = + Hmac::::new_from_slice(secret).map_err(|_| Error("invalid_webhook_secret"))?; + signer.update(timestamp.as_bytes()); + signer.update(b"."); + signer.update(body); + let signature = signer + .finalize() + .into_bytes() + .iter() + .map(|v| format!("{v:02x}")) + .collect::(); + let signature = format!("sha256={signature}"); + match post( + &endpoint, + "application/json", + body, + &[ + ("Msg-Event-Id", event), + ("Msg-Delivery-Id", delivery), + ("Msg-Timestamp", timestamp), + ("Msg-Signature", &signature), + ], + )? { + 200..=299 => Ok(EffectOutcome::Done(status("delivered"))), + 408 | 429 | 500..=599 => Ok(EffectOutcome::Retry { + code: "webhook_retry_scheduled", + exhausted: "webhook_attempts_exhausted", + }), + _ => Ok(EffectOutcome::Failed("failed")), + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn public_delivery_rejects_private_mapped_and_mixed_targets() { + for raw in [ + "127.0.0.1", + "10.1.2.3", + "100.64.0.1", + "169.254.169.254", + "192.0.0.8", + "224.1.2.3", + "::1", + "::ffff:127.0.0.1", + "fc00::1", + "fe80::1", + "2001:db8::1", + "64:ff9b:1::1", + "2002::1", + ] { + assert!(require_public(raw.parse().unwrap()).is_err(), "{raw}"); + } + for raw in [ + "1.1.1.1", + "192.0.0.9", + "2606:4700:4700::1111", + "::ffff:1.1.1.1", + ] { + assert!(require_public(raw.parse().unwrap()).is_ok(), "{raw}"); + } + } + #[test] + fn endpoint_has_no_credential_redirect_or_parser_ambiguity() { + for raw in [ + "http://example.com/", + "https://user@example.com/", + "https://example.com:444/", + "https://example.com/#fragment", + "https://127.0.0.1/", + "https://[::1]/", + "https://example.com\\@localhost/", + "https://example.com/\r\nx:y", + ] { + assert!(Endpoint::parse(raw).is_err(), "{raw}"); + } + let endpoint = Endpoint::parse("https://EXAMPLE.com.:443?x=1").unwrap(); + assert_eq!(endpoint.host(), "example.com"); + assert_eq!(endpoint.target, "/?x=1"); + } +} diff --git a/rust/crates/msg-worker/src/tests.rs b/rust/crates/msg-worker/src/tests.rs new file mode 100644 index 00000000..bd93c1b0 --- /dev/null +++ b/rust/crates/msg-worker/src/tests.rs @@ -0,0 +1,209 @@ +use super::*; +use std::cell::RefCell; + +struct Memory { + jobs: RefCell>, + statuses: RefCell>, +} +impl Memory { + fn new(jobs: Vec) -> Self { + Self { + jobs: RefCell::new(jobs.into_iter().map(|j| (j.id.clone(), j)).collect()), + statuses: RefCell::new(BTreeMap::new()), + } + } +} +impl JobStore for Memory { + fn job(&self, id: &str) -> Result { + self.jobs + .borrow() + .get(id) + .cloned() + .ok_or(Error("job_not_found")) + } + fn job_page(&self, scan: JobScan, after: Option<&JobCursor>) -> Result> { + let mut jobs = self + .jobs + .borrow() + .values() + .filter(|job| match scan { + JobScan::Running => job.state == "running", + JobScan::PendingDue(now) => job.state == "pending" && job.next_attempt_at <= now, + }) + .filter(|job| { + after.is_none_or(|c| { + (job.next_attempt_at, job.id.as_str()) > (c.next_at, c.id.as_str()) + }) + }) + .cloned() + .collect::>(); + jobs.sort_by(|a, b| (a.next_attempt_at, &a.id).cmp(&(b.next_attempt_at, &b.id))); + jobs.truncate(PAGE_SIZE); + Ok(jobs) + } + fn save_job_if_attempt(&self, job: &EffectJob, state: &str, attempt: i64) -> Result { + let mut jobs = self.jobs.borrow_mut(); + let current = jobs.get_mut(&job.id).ok_or(Error("job_not_found"))?; + if current.state != state || current.attempts != attempt { + return Ok(false); + } + *current = job.clone(); + Ok(true) + } + fn job_status(&self, id: &str, status: &Json) -> Result<()> { + self.statuses.borrow_mut().insert(id.into(), status.clone()); + Ok(()) + } +} +fn fixture(id: &str, kind: &str, state: &str) -> EffectJob { + let mut job: EffectJob = decode(&Json::parse(r#"{"id":"job_test","event_id":"evt_test","kind":"mail","dedupe_key":"x","principal":{},"operation":"communication.send","arguments":{},"state":"pending","attempts":0,"next_attempt_at":"2026-01-01T00:00:00Z","lease_until":null}"#).unwrap()).unwrap(); + job.id = id.into(); + job.kind = kind.into(); + job.state = state.into(); + if kind == "tool" { + job.arguments.insert( + "tool".into(), + Json::parse(r#"{"id":"tool_http","revision":"rev_1"}"#).unwrap(), + ); + } + if state == "running" { + job.attempts = 1; + job.lease_until = Some(advance(job.next_attempt_at, 120).unwrap()); + } + job +} + +#[test] +fn shutdown_during_policy_read_never_claims_or_starts_an_attempt() { + let job = fixture("job_1", "tool", "pending"); + let now = job.next_attempt_at; + let store = Memory::new(vec![job]); + let stopping = AtomicBool::new(false); + let result = claim(&store, now, 120, &stopping, |_| { + stopping.store(true, Ordering::Release); + Ok(1) + }) + .unwrap(); + assert!(matches!(result, Claim::None)); + assert_eq!(store.job("job_1").unwrap().attempts, 0); + assert_eq!(store.job("job_1").unwrap().state, "pending"); +} +#[test] +fn orphaned_external_work_is_never_requeued_but_websub_hints_are() { + for (kind, expected) in [ + ("mail", "uncertain"), + ("webhook", "uncertain"), + ("git.push", "uncertain"), + ("websub", "pending"), + ] { + let mut job = fixture("job_1", kind, "running"); + let now = job.lease_until.unwrap(); + job.lease_until = Some(now); + let store = Memory::new(vec![job]); + let result = claim(&store, now, 120, &AtomicBool::new(false), |_| { + panic!("not a tool") + }) + .unwrap(); + assert!(matches!(result, Claim::Swept)); + let current = store.job("job_1").unwrap(); + assert_eq!(current.state, expected); + assert!(current.lease_until.is_none()); + if kind == "websub" { + assert!(current.next_attempt_at == advance(now, 30).unwrap()); + } + } +} +#[test] +fn tool_limit_counts_other_workers_before_claiming_due_jobs() { + let running = fixture("job_running", "tool", "running"); + let mut blocked = fixture("job_1", "tool", "pending"); + blocked.arguments.insert( + "tool".into(), + Json::parse(r#"{"id":"tool_http","revision":"rev_2"}"#).unwrap(), + ); + let mail = fixture("job_2", "mail", "pending"); + let now = mail.next_attempt_at; + let store = Memory::new(vec![running, blocked, mail]); + let result = claim(&store, now, 120, &AtomicBool::new(false), |reference| { + assert_eq!(reference.revision.as_deref(), Some("rev_2")); + Ok(1) + }) + .unwrap(); + let Claim::Job(job) = result else { + panic!("a different effect must remain claimable") + }; + assert_eq!(job.id, "job_2"); + assert_eq!(job.attempts, 1); + assert_eq!(store.job("job_1").unwrap().state, "pending"); +} +#[test] +fn scans_continue_past_one_page_before_claiming() { + let mut jobs = (0..65) + .map(|i| fixture(&format!("job_running_{i:03}"), "mail", "running")) + .collect::>(); + let now = jobs[0].next_attempt_at; + jobs[64].lease_until = None; + jobs.push(fixture("job_pending", "mail", "pending")); + let store = Memory::new(jobs); + assert!(matches!( + claim( + &store, + now, + 120, + &AtomicBool::new(false), + |_| unreachable!() + ) + .unwrap(), + Claim::Swept + )); + assert_eq!(store.job("job_running_064").unwrap().state, "uncertain"); + assert_eq!(store.job("job_pending").unwrap().state, "pending"); +} +#[test] +fn exhausted_websub_and_live_retry_are_bounded() { + let mut job = fixture("job_1", "websub", "running"); + job.attempts = 8; + let now = job.lease_until.unwrap(); + let store = Memory::new(vec![job]); + claim( + &store, + now, + 120, + &AtomicBool::new(false), + |_| unreachable!(), + ) + .unwrap(); + assert_eq!(store.job("job_1").unwrap().state, "failed"); + assert!(store.statuses.borrow()["job_1"] == jobs::status("websub_attempts_exhausted")); + let job = fixture("job_2", "webhook", "running"); + let now = job.next_attempt_at; + let store = Memory::new(vec![job.clone()]); + complete( + &store, + &job, + now, + EffectOutcome::Retry { + code: "retry", + exhausted: "exhausted", + }, + ) + .unwrap(); + let current = store.job("job_2").unwrap(); + assert_eq!(current.state, "pending"); + assert!(current.next_attempt_at == advance(now, 30).unwrap()); + complete(&store, &job, now, EffectOutcome::Done(jobs::status("late"))).unwrap(); + assert_eq!(store.job("job_2").unwrap().state, "pending"); +} +#[test] +fn queued_contract_version_rejects_boolean_zero_and_fraction() { + let mut job = fixture("job_1", "mail", "pending"); + assert_eq!(contract_version(&job).unwrap(), 1); + for raw in ["true", "0", "1.0", "-1", "4294967296"] { + job.arguments + .insert("contract_version".into(), Json::parse(raw).unwrap()); + assert_eq!( + contract_version(&job), + Err(Error("invalid_job_contract_version")) + ); + } +} From 3872f9689720c24edb8005ca73c674ef60ce4fd3 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Thu, 22 Oct 2026 06:59:22 +0800 Subject: [PATCH 06/13] Fix daemon build features and export full compatibility contracts --- rust/Cargo.toml | 4 +++- rust/tools/export_catalog.py | 21 +++++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index fbcde9ba..539a747c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -17,6 +17,8 @@ url = "=2.5.8" reqwest = { version = "=0.12.28", default-features = false, features = ["blocking", "native-tls"] } hickory-resolver = { version = "=0.24.4", features = ["system-config", "tokio-runtime"] } jsonschema = { version = "=0.33.0", default-features = false } +unicode-normalization = "=0.1.26" +roxmltree = "=0.21.1" aes-gcm = "=0.10.3" base64 = "=0.22.1" chrono = { version = "=0.4.42", default-features = false, features = ["std"] } @@ -24,7 +26,7 @@ ed25519-dalek = "=2.2.0" hkdf = "=0.12.4" hmac = "=0.12.1" rand_core = { version = "=0.6.4", features = ["getrandom"] } -rustix = { version = "=1.1.5", features = ["process", "resource"] } +rustix = { version = "=1.1.5", features = ["process"] } ryu = "=1.0.20" serde = { version = "=1.0.228", features = ["derive"] } serde_json = { version = "=1.0.145", features = ["raw_value", "arbitrary_precision"] } diff --git a/rust/tools/export_catalog.py b/rust/tools/export_catalog.py index d844719d..30f5471e 100644 --- a/rust/tools/export_catalog.py +++ b/rust/tools/export_catalog.py @@ -3,6 +3,7 @@ import argparse import inspect import json +import unicodedata from pathlib import Path from types import SimpleNamespace @@ -17,15 +18,13 @@ def catalog(): app = Application(SimpleNamespace(server=SimpleNamespace(plugins=BUILTINS))) registry = app.registry operations = [] - schemas = {} + schemas = dict(registry._schemas) for spec in registry.operations(): row = registry.describe(spec) for ref in (spec.input_schema, spec.output_schema): schemas[ref.id] = registry.schema(ref) row['handler'] = spec.handler.__module__ + ':' + spec.handler.__qualname__ - row['requirements'] = ( - spec.requirements.__module__ + ':' + spec.requirements.__qualname__ - ) + row['requirements'] = spec.requirements.__module__ + ':' + spec.requirements.__qualname__ if spec.requirements.__qualname__ == 'requirement..requirements': closure = inspect.getclosurevars(spec.requirements).nonlocals row['access'] = {key: closure[key] for key in ('argument', 'check', 'default')} @@ -33,6 +32,20 @@ def catalog(): return { 'version': 1, 'plugins': list(BUILTINS), + 'plugin_manifests': { + name: { + 'dependencies': list(manifest.dependencies), + 'operations': [f'{spec.name}@{spec.version}' for spec in manifest.operations], + } + for name, manifest in registry._plugins.items() + }, + 'unicode_version': unicodedata.unidata_version, + 'casefold': { + char: char.casefold() + for number in range(0x110000) + if not 0xD800 <= number <= 0xDFFF + and (char := chr(number)).casefold() != char + }, 'operations': operations, 'schemas': schemas, 'capabilities': wire(registry.capabilities()), From c47330864d404db3dba936f265b68991d5765237 Mon Sep 17 00:00:00 2001 From: LIghtJUNction Date: Thu, 22 Oct 2026 07:05:42 +0800 Subject: [PATCH 07/13] Integrate native identity, content recovery and daemon runtime contracts --- .github/workflows/rust-server.yml | 40 + rust/Cargo.lock | 22 + rust/Cargo.toml | 5 +- rust/assets/casefold.json | 1 + rust/assets/catalog.json | 1 + rust/crates/msg-crypto/src/age_recipient.rs | 85 +- rust/crates/msg-crypto/src/lib.rs | 1 + rust/crates/msg-crypto/src/vault_box.rs | 62 + .../msg-identity/src/certificate_lifecycle.rs | 290 ++++ rust/crates/msg-identity/src/lib.rs | 2 + rust/crates/msg-identity/src/lifecycle.rs | 117 +- rust/crates/msg-identity/src/mutations.rs | 9 +- rust/crates/msg-identity/src/vault.rs | 243 +++ rust/crates/msg-runtime/Cargo.toml | 34 + rust/crates/msg-runtime/src/catalog.rs | 370 ++++ rust/crates/msg-runtime/src/config.rs | 883 ++++++++++ rust/crates/msg-runtime/src/content.rs | 1515 +++++++++++++++++ rust/crates/msg-runtime/src/content/tests.rs | 130 ++ rust/crates/msg-runtime/src/context.rs | 227 +++ rust/crates/msg-runtime/src/discovery.rs | 591 +++++++ rust/crates/msg-runtime/src/lib.rs | 6 + rust/crates/msg-runtime/src/unicode.rs | 42 + .../examples/identity_lifecycle_check.rs | 215 +++ .../msg-storage/src/content/collection.rs | 217 +++ .../src/content/collection/tests.rs | 187 ++ rust/crates/msg-storage/src/content/mod.rs | 2 + .../msg-storage/src/content/recovery.rs | 2 +- .../msg-storage/src/postgres/identity.rs | 83 +- rust/crates/msg-worker/Cargo.toml | 5 + .../msg-worker/src/bin/msg-tool-sandbox.rs | 3 + rust/crates/msg-worker/src/git.rs | 429 +++++ rust/crates/msg-worker/src/lib.rs | 95 +- rust/crates/msg-worker/src/network.rs | 315 ++++ rust/crates/msg-worker/src/process.rs | 188 ++ rust/crates/msg-worker/src/sandbox.rs | 731 ++++++++ rust/crates/msg-worker/src/tool.rs | 431 +++++ rust/tests/test_identity_lifecycle.py | 330 ++++ rust/tools/export_catalog.py | 3 +- 38 files changed, 7861 insertions(+), 51 deletions(-) create mode 100644 rust/assets/casefold.json create mode 100644 rust/assets/catalog.json create mode 100644 rust/crates/msg-crypto/src/vault_box.rs create mode 100644 rust/crates/msg-identity/src/certificate_lifecycle.rs create mode 100644 rust/crates/msg-identity/src/vault.rs create mode 100644 rust/crates/msg-runtime/Cargo.toml create mode 100644 rust/crates/msg-runtime/src/catalog.rs create mode 100644 rust/crates/msg-runtime/src/config.rs create mode 100644 rust/crates/msg-runtime/src/content.rs create mode 100644 rust/crates/msg-runtime/src/content/tests.rs create mode 100644 rust/crates/msg-runtime/src/context.rs create mode 100644 rust/crates/msg-runtime/src/discovery.rs create mode 100644 rust/crates/msg-runtime/src/lib.rs create mode 100644 rust/crates/msg-runtime/src/unicode.rs create mode 100644 rust/crates/msg-storage/examples/identity_lifecycle_check.rs create mode 100644 rust/crates/msg-storage/src/content/collection.rs create mode 100644 rust/crates/msg-storage/src/content/collection/tests.rs create mode 100644 rust/crates/msg-worker/src/bin/msg-tool-sandbox.rs create mode 100644 rust/crates/msg-worker/src/git.rs create mode 100644 rust/crates/msg-worker/src/network.rs create mode 100644 rust/crates/msg-worker/src/process.rs create mode 100644 rust/crates/msg-worker/src/sandbox.rs create mode 100644 rust/crates/msg-worker/src/tool.rs create mode 100644 rust/tests/test_identity_lifecycle.py diff --git a/.github/workflows/rust-server.yml b/.github/workflows/rust-server.yml index 24c2e730..9c2bd01d 100644 --- a/.github/workflows/rust-server.yml +++ b/.github/workflows/rust-server.yml @@ -8,6 +8,46 @@ on: permissions: contents: read jobs: + identity: + runs-on: ubuntu-latest + timeout-minutes: 20 + services: + postgres: + image: postgres:16 + env: + POSTGRES_PASSWORD: postgres + ports: ['5432:5432'] + options: >- + --health-cmd pg_isready + --health-interval 5s + --health-timeout 5s + --health-retries 12 + env: + CARGO_RESOLVER_INCOMPATIBLE_RUST_VERSIONS: fallback + MSG_PG_TEST_ADMIN_DSN: postgresql://postgres:postgres@127.0.0.1:5432/postgres + PYTHONPATH: src + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: '3.15' + - name: Install native and reference dependencies + run: | + rustup toolchain install 1.85.0 --profile minimal + python -m pip install 'cryptography==46.0.3' 'jsonschema>=4.25,<5' 'referencing>=0.36,<1' 'psycopg[binary]>=3.2,<4' + - name: Build native identity fixture + working-directory: rust + run: cargo +1.85.0 build -p msg-storage --example identity_lifecycle_check + - name: PostgreSQL issuance and browser interoperation + run: python rust/tests/test_identity_lifecycle.py --binary rust/target/debug/examples/identity_lifecycle_check --report rust/artifacts/identity-lifecycle.json + - uses: actions/upload-artifact@v7 + if: ${{ always() }} + with: + name: daemon-identity-${{ github.sha }} + path: rust/artifacts/identity-lifecycle.json + if-no-files-found: warn native: runs-on: ubuntu-latest timeout-minutes: 20 diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 47973ea8..96f1b8b6 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -718,13 +718,16 @@ name = "msg-identity" version = "0.1.0" dependencies = [ "chrono", + "hmac 0.12.1", "msg-core", "msg-crypto", "msg-protocol", + "rand_core 0.6.4", "serde", "serde_json", "sha2 0.10.9", "subtle", + "zeroize", ] [[package]] @@ -757,6 +760,25 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "msg-worker" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "base64", + "chrono", + "hmac 0.12.1", + "libc", + "msg-core", + "msg-identity", + "msg-storage", + "native-tls", + "serde", + "serde_json", + "sha2 0.10.9", + "zeroize", +] + [[package]] name = "native-tls" version = "0.2.14" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 539a747c..f5e0f7cd 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/msg-core", "crates/msg-crypto", "crates/msg-protocol", "crates/msg-identity", "crates/msg-storage", "crates/msg-worker"] +members = ["crates/msg-core", "crates/msg-crypto", "crates/msg-protocol", "crates/msg-identity", "crates/msg-storage", "crates/msg-worker", "crates/msg-runtime"] resolver = "2" [workspace.package] @@ -19,6 +19,7 @@ hickory-resolver = { version = "=0.24.4", features = ["system-config", "tokio-ru jsonschema = { version = "=0.33.0", default-features = false } unicode-normalization = "=0.1.26" roxmltree = "=0.21.1" +regex = "=1.12.2" aes-gcm = "=0.10.3" base64 = "=0.22.1" chrono = { version = "=0.4.42", default-features = false, features = ["std"] } @@ -26,7 +27,7 @@ ed25519-dalek = "=2.2.0" hkdf = "=0.12.4" hmac = "=0.12.1" rand_core = { version = "=0.6.4", features = ["getrandom"] } -rustix = { version = "=1.1.5", features = ["process"] } +rustix = { version = "=1.1.5", features = ["process", "fs"] } ryu = "=1.0.20" serde = { version = "=1.0.228", features = ["derive"] } serde_json = { version = "=1.0.145", features = ["raw_value", "arbitrary_precision"] } diff --git a/rust/assets/casefold.json b/rust/assets/casefold.json new file mode 100644 index 00000000..8b64b34d --- /dev/null +++ b/rust/assets/casefold.json @@ -0,0 +1 @@ +{"version":"17.0.0","mapping":{"A":"a","B":"b","C":"c","D":"d","E":"e","F":"f","G":"g","H":"h","I":"i","J":"j","K":"k","L":"l","M":"m","N":"n","O":"o","P":"p","Q":"q","R":"r","S":"s","T":"t","U":"u","V":"v","W":"w","X":"x","Y":"y","Z":"z","µ":"μ","À":"à","Á":"á","Â":"â","Ã":"ã","Ä":"ä","Å":"å","Æ":"æ","Ç":"ç","È":"è","É":"é","Ê":"ê","Ë":"ë","Ì":"ì","Í":"í","Î":"î","Ï":"ï","Ð":"ð","Ñ":"ñ","Ò":"ò","Ó":"ó","Ô":"ô","Õ":"õ","Ö":"ö","Ø":"ø","Ù":"ù","Ú":"ú","Û":"û","Ü":"ü","Ý":"ý","Þ":"þ","ß":"ss","Ā":"ā","Ă":"ă","Ą":"ą","Ć":"ć","Ĉ":"ĉ","Ċ":"ċ","Č":"č","Ď":"ď","Đ":"đ","Ē":"ē","Ĕ":"ĕ","Ė":"ė","Ę":"ę","Ě":"ě","Ĝ":"ĝ","Ğ":"ğ","Ġ":"ġ","Ģ":"ģ","Ĥ":"ĥ","Ħ":"ħ","Ĩ":"ĩ","Ī":"ī","Ĭ":"ĭ","Į":"į","İ":"i̇","IJ":"ij","Ĵ":"ĵ","Ķ":"ķ","Ĺ":"ĺ","Ļ":"ļ","Ľ":"ľ","Ŀ":"ŀ","Ł":"ł","Ń":"ń","Ņ":"ņ","Ň":"ň","ʼn":"ʼn","Ŋ":"ŋ","Ō":"ō","Ŏ":"ŏ","Ő":"ő","Œ":"œ","Ŕ":"ŕ","Ŗ":"ŗ","Ř":"ř","Ś":"ś","Ŝ":"ŝ","Ş":"ş","Š":"š","Ţ":"ţ","Ť":"ť","Ŧ":"ŧ","Ũ":"ũ","Ū":"ū","Ŭ":"ŭ","Ů":"ů","Ű":"ű","Ų":"ų","Ŵ":"ŵ","Ŷ":"ŷ","Ÿ":"ÿ","Ź":"ź","Ż":"ż","Ž":"ž","ſ":"s","Ɓ":"ɓ","Ƃ":"ƃ","Ƅ":"ƅ","Ɔ":"ɔ","Ƈ":"ƈ","Ɖ":"ɖ","Ɗ":"ɗ","Ƌ":"ƌ","Ǝ":"ǝ","Ə":"ə","Ɛ":"ɛ","Ƒ":"ƒ","Ɠ":"ɠ","Ɣ":"ɣ","Ɩ":"ɩ","Ɨ":"ɨ","Ƙ":"ƙ","Ɯ":"ɯ","Ɲ":"ɲ","Ɵ":"ɵ","Ơ":"ơ","Ƣ":"ƣ","Ƥ":"ƥ","Ʀ":"ʀ","Ƨ":"ƨ","Ʃ":"ʃ","Ƭ":"ƭ","Ʈ":"ʈ","Ư":"ư","Ʊ":"ʊ","Ʋ":"ʋ","Ƴ":"ƴ","Ƶ":"ƶ","Ʒ":"ʒ","Ƹ":"ƹ","Ƽ":"ƽ","DŽ":"dž","Dž":"dž","LJ":"lj","Lj":"lj","NJ":"nj","Nj":"nj","Ǎ":"ǎ","Ǐ":"ǐ","Ǒ":"ǒ","Ǔ":"ǔ","Ǖ":"ǖ","Ǘ":"ǘ","Ǚ":"ǚ","Ǜ":"ǜ","Ǟ":"ǟ","Ǡ":"ǡ","Ǣ":"ǣ","Ǥ":"ǥ","Ǧ":"ǧ","Ǩ":"ǩ","Ǫ":"ǫ","Ǭ":"ǭ","Ǯ":"ǯ","ǰ":"ǰ","DZ":"dz","Dz":"dz","Ǵ":"ǵ","Ƕ":"ƕ","Ƿ":"ƿ","Ǹ":"ǹ","Ǻ":"ǻ","Ǽ":"ǽ","Ǿ":"ǿ","Ȁ":"ȁ","Ȃ":"ȃ","Ȅ":"ȅ","Ȇ":"ȇ","Ȉ":"ȉ","Ȋ":"ȋ","Ȍ":"ȍ","Ȏ":"ȏ","Ȑ":"ȑ","Ȓ":"ȓ","Ȕ":"ȕ","Ȗ":"ȗ","Ș":"ș","Ț":"ț","Ȝ":"ȝ","Ȟ":"ȟ","Ƞ":"ƞ","Ȣ":"ȣ","Ȥ":"ȥ","Ȧ":"ȧ","Ȩ":"ȩ","Ȫ":"ȫ","Ȭ":"ȭ","Ȯ":"ȯ","Ȱ":"ȱ","Ȳ":"ȳ","Ⱥ":"ⱥ","Ȼ":"ȼ","Ƚ":"ƚ","Ⱦ":"ⱦ","Ɂ":"ɂ","Ƀ":"ƀ","Ʉ":"ʉ","Ʌ":"ʌ","Ɇ":"ɇ","Ɉ":"ɉ","Ɋ":"ɋ","Ɍ":"ɍ","Ɏ":"ɏ","ͅ":"ι","Ͱ":"ͱ","Ͳ":"ͳ","Ͷ":"ͷ","Ϳ":"ϳ","Ά":"ά","Έ":"έ","Ή":"ή","Ί":"ί","Ό":"ό","Ύ":"ύ","Ώ":"ώ","ΐ":"ΐ","Α":"α","Β":"β","Γ":"γ","Δ":"δ","Ε":"ε","Ζ":"ζ","Η":"η","Θ":"θ","Ι":"ι","Κ":"κ","Λ":"λ","Μ":"μ","Ν":"ν","Ξ":"ξ","Ο":"ο","Π":"π","Ρ":"ρ","Σ":"σ","Τ":"τ","Υ":"υ","Φ":"φ","Χ":"χ","Ψ":"ψ","Ω":"ω","Ϊ":"ϊ","Ϋ":"ϋ","ΰ":"ΰ","ς":"σ","Ϗ":"ϗ","ϐ":"β","ϑ":"θ","ϕ":"φ","ϖ":"π","Ϙ":"ϙ","Ϛ":"ϛ","Ϝ":"ϝ","Ϟ":"ϟ","Ϡ":"ϡ","Ϣ":"ϣ","Ϥ":"ϥ","Ϧ":"ϧ","Ϩ":"ϩ","Ϫ":"ϫ","Ϭ":"ϭ","Ϯ":"ϯ","ϰ":"κ","ϱ":"ρ","ϴ":"θ","ϵ":"ε","Ϸ":"ϸ","Ϲ":"ϲ","Ϻ":"ϻ","Ͻ":"ͻ","Ͼ":"ͼ","Ͽ":"ͽ","Ѐ":"ѐ","Ё":"ё","Ђ":"ђ","Ѓ":"ѓ","Є":"є","Ѕ":"ѕ","І":"і","Ї":"ї","Ј":"ј","Љ":"љ","Њ":"њ","Ћ":"ћ","Ќ":"ќ","Ѝ":"ѝ","Ў":"ў","Џ":"џ","А":"а","Б":"б","В":"в","Г":"г","Д":"д","Е":"е","Ж":"ж","З":"з","И":"и","Й":"й","К":"к","Л":"л","М":"м","Н":"н","О":"о","П":"п","Р":"р","С":"с","Т":"т","У":"у","Ф":"ф","Х":"х","Ц":"ц","Ч":"ч","Ш":"ш","Щ":"щ","Ъ":"ъ","Ы":"ы","Ь":"ь","Э":"э","Ю":"ю","Я":"я","Ѡ":"ѡ","Ѣ":"ѣ","Ѥ":"ѥ","Ѧ":"ѧ","Ѩ":"ѩ","Ѫ":"ѫ","Ѭ":"ѭ","Ѯ":"ѯ","Ѱ":"ѱ","Ѳ":"ѳ","Ѵ":"ѵ","Ѷ":"ѷ","Ѹ":"ѹ","Ѻ":"ѻ","Ѽ":"ѽ","Ѿ":"ѿ","Ҁ":"ҁ","Ҋ":"ҋ","Ҍ":"ҍ","Ҏ":"ҏ","Ґ":"ґ","Ғ":"ғ","Ҕ":"ҕ","Җ":"җ","Ҙ":"ҙ","Қ":"қ","Ҝ":"ҝ","Ҟ":"ҟ","Ҡ":"ҡ","Ң":"ң","Ҥ":"ҥ","Ҧ":"ҧ","Ҩ":"ҩ","Ҫ":"ҫ","Ҭ":"ҭ","Ү":"ү","Ұ":"ұ","Ҳ":"ҳ","Ҵ":"ҵ","Ҷ":"ҷ","Ҹ":"ҹ","Һ":"һ","Ҽ":"ҽ","Ҿ":"ҿ","Ӏ":"ӏ","Ӂ":"ӂ","Ӄ":"ӄ","Ӆ":"ӆ","Ӈ":"ӈ","Ӊ":"ӊ","Ӌ":"ӌ","Ӎ":"ӎ","Ӑ":"ӑ","Ӓ":"ӓ","Ӕ":"ӕ","Ӗ":"ӗ","Ә":"ә","Ӛ":"ӛ","Ӝ":"ӝ","Ӟ":"ӟ","Ӡ":"ӡ","Ӣ":"ӣ","Ӥ":"ӥ","Ӧ":"ӧ","Ө":"ө","Ӫ":"ӫ","Ӭ":"ӭ","Ӯ":"ӯ","Ӱ":"ӱ","Ӳ":"ӳ","Ӵ":"ӵ","Ӷ":"ӷ","Ӹ":"ӹ","Ӻ":"ӻ","Ӽ":"ӽ","Ӿ":"ӿ","Ԁ":"ԁ","Ԃ":"ԃ","Ԅ":"ԅ","Ԇ":"ԇ","Ԉ":"ԉ","Ԋ":"ԋ","Ԍ":"ԍ","Ԏ":"ԏ","Ԑ":"ԑ","Ԓ":"ԓ","Ԕ":"ԕ","Ԗ":"ԗ","Ԙ":"ԙ","Ԛ":"ԛ","Ԝ":"ԝ","Ԟ":"ԟ","Ԡ":"ԡ","Ԣ":"ԣ","Ԥ":"ԥ","Ԧ":"ԧ","Ԩ":"ԩ","Ԫ":"ԫ","Ԭ":"ԭ","Ԯ":"ԯ","Ա":"ա","Բ":"բ","Գ":"գ","Դ":"դ","Ե":"ե","Զ":"զ","Է":"է","Ը":"ը","Թ":"թ","Ժ":"ժ","Ի":"ի","Լ":"լ","Խ":"խ","Ծ":"ծ","Կ":"կ","Հ":"հ","Ձ":"ձ","Ղ":"ղ","Ճ":"ճ","Մ":"մ","Յ":"յ","Ն":"ն","Շ":"շ","Ո":"ո","Չ":"չ","Պ":"պ","Ջ":"ջ","Ռ":"ռ","Ս":"ս","Վ":"վ","Տ":"տ","Ր":"ր","Ց":"ց","Ւ":"ւ","Փ":"փ","Ք":"ք","Օ":"օ","Ֆ":"ֆ","և":"եւ","Ⴀ":"ⴀ","Ⴁ":"ⴁ","Ⴂ":"ⴂ","Ⴃ":"ⴃ","Ⴄ":"ⴄ","Ⴅ":"ⴅ","Ⴆ":"ⴆ","Ⴇ":"ⴇ","Ⴈ":"ⴈ","Ⴉ":"ⴉ","Ⴊ":"ⴊ","Ⴋ":"ⴋ","Ⴌ":"ⴌ","Ⴍ":"ⴍ","Ⴎ":"ⴎ","Ⴏ":"ⴏ","Ⴐ":"ⴐ","Ⴑ":"ⴑ","Ⴒ":"ⴒ","Ⴓ":"ⴓ","Ⴔ":"ⴔ","Ⴕ":"ⴕ","Ⴖ":"ⴖ","Ⴗ":"ⴗ","Ⴘ":"ⴘ","Ⴙ":"ⴙ","Ⴚ":"ⴚ","Ⴛ":"ⴛ","Ⴜ":"ⴜ","Ⴝ":"ⴝ","Ⴞ":"ⴞ","Ⴟ":"ⴟ","Ⴠ":"ⴠ","Ⴡ":"ⴡ","Ⴢ":"ⴢ","Ⴣ":"ⴣ","Ⴤ":"ⴤ","Ⴥ":"ⴥ","Ⴧ":"ⴧ","Ⴭ":"ⴭ","ᏸ":"Ᏸ","ᏹ":"Ᏹ","ᏺ":"Ᏺ","ᏻ":"Ᏻ","ᏼ":"Ᏼ","ᏽ":"Ᏽ","ᲀ":"в","ᲁ":"д","ᲂ":"о","ᲃ":"с","ᲄ":"т","ᲅ":"т","ᲆ":"ъ","ᲇ":"ѣ","ᲈ":"ꙋ","Ᲊ":"ᲊ","Ა":"ა","Ბ":"ბ","Გ":"გ","Დ":"დ","Ე":"ე","Ვ":"ვ","Ზ":"ზ","Თ":"თ","Ი":"ი","Კ":"კ","Ლ":"ლ","Მ":"მ","Ნ":"ნ","Ო":"ო","Პ":"პ","Ჟ":"ჟ","Რ":"რ","Ს":"ს","Ტ":"ტ","Უ":"უ","Ფ":"ფ","Ქ":"ქ","Ღ":"ღ","Ყ":"ყ","Შ":"შ","Ჩ":"ჩ","Ც":"ც","Ძ":"ძ","Წ":"წ","Ჭ":"ჭ","Ხ":"ხ","Ჯ":"ჯ","Ჰ":"ჰ","Ჱ":"ჱ","Ჲ":"ჲ","Ჳ":"ჳ","Ჴ":"ჴ","Ჵ":"ჵ","Ჶ":"ჶ","Ჷ":"ჷ","Ჸ":"ჸ","Ჹ":"ჹ","Ჺ":"ჺ","Ჽ":"ჽ","Ჾ":"ჾ","Ჿ":"ჿ","Ḁ":"ḁ","Ḃ":"ḃ","Ḅ":"ḅ","Ḇ":"ḇ","Ḉ":"ḉ","Ḋ":"ḋ","Ḍ":"ḍ","Ḏ":"ḏ","Ḑ":"ḑ","Ḓ":"ḓ","Ḕ":"ḕ","Ḗ":"ḗ","Ḙ":"ḙ","Ḛ":"ḛ","Ḝ":"ḝ","Ḟ":"ḟ","Ḡ":"ḡ","Ḣ":"ḣ","Ḥ":"ḥ","Ḧ":"ḧ","Ḩ":"ḩ","Ḫ":"ḫ","Ḭ":"ḭ","Ḯ":"ḯ","Ḱ":"ḱ","Ḳ":"ḳ","Ḵ":"ḵ","Ḷ":"ḷ","Ḹ":"ḹ","Ḻ":"ḻ","Ḽ":"ḽ","Ḿ":"ḿ","Ṁ":"ṁ","Ṃ":"ṃ","Ṅ":"ṅ","Ṇ":"ṇ","Ṉ":"ṉ","Ṋ":"ṋ","Ṍ":"ṍ","Ṏ":"ṏ","Ṑ":"ṑ","Ṓ":"ṓ","Ṕ":"ṕ","Ṗ":"ṗ","Ṙ":"ṙ","Ṛ":"ṛ","Ṝ":"ṝ","Ṟ":"ṟ","Ṡ":"ṡ","Ṣ":"ṣ","Ṥ":"ṥ","Ṧ":"ṧ","Ṩ":"ṩ","Ṫ":"ṫ","Ṭ":"ṭ","Ṯ":"ṯ","Ṱ":"ṱ","Ṳ":"ṳ","Ṵ":"ṵ","Ṷ":"ṷ","Ṹ":"ṹ","Ṻ":"ṻ","Ṽ":"ṽ","Ṿ":"ṿ","Ẁ":"ẁ","Ẃ":"ẃ","Ẅ":"ẅ","Ẇ":"ẇ","Ẉ":"ẉ","Ẋ":"ẋ","Ẍ":"ẍ","Ẏ":"ẏ","Ẑ":"ẑ","Ẓ":"ẓ","Ẕ":"ẕ","ẖ":"ẖ","ẗ":"ẗ","ẘ":"ẘ","ẙ":"ẙ","ẚ":"aʾ","ẛ":"ṡ","ẞ":"ss","Ạ":"ạ","Ả":"ả","Ấ":"ấ","Ầ":"ầ","Ẩ":"ẩ","Ẫ":"ẫ","Ậ":"ậ","Ắ":"ắ","Ằ":"ằ","Ẳ":"ẳ","Ẵ":"ẵ","Ặ":"ặ","Ẹ":"ẹ","Ẻ":"ẻ","Ẽ":"ẽ","Ế":"ế","Ề":"ề","Ể":"ể","Ễ":"ễ","Ệ":"ệ","Ỉ":"ỉ","Ị":"ị","Ọ":"ọ","Ỏ":"ỏ","Ố":"ố","Ồ":"ồ","Ổ":"ổ","Ỗ":"ỗ","Ộ":"ộ","Ớ":"ớ","Ờ":"ờ","Ở":"ở","Ỡ":"ỡ","Ợ":"ợ","Ụ":"ụ","Ủ":"ủ","Ứ":"ứ","Ừ":"ừ","Ử":"ử","Ữ":"ữ","Ự":"ự","Ỳ":"ỳ","Ỵ":"ỵ","Ỷ":"ỷ","Ỹ":"ỹ","Ỻ":"ỻ","Ỽ":"ỽ","Ỿ":"ỿ","Ἀ":"ἀ","Ἁ":"ἁ","Ἂ":"ἂ","Ἃ":"ἃ","Ἄ":"ἄ","Ἅ":"ἅ","Ἆ":"ἆ","Ἇ":"ἇ","Ἐ":"ἐ","Ἑ":"ἑ","Ἒ":"ἒ","Ἓ":"ἓ","Ἔ":"ἔ","Ἕ":"ἕ","Ἠ":"ἠ","Ἡ":"ἡ","Ἢ":"ἢ","Ἣ":"ἣ","Ἤ":"ἤ","Ἥ":"ἥ","Ἦ":"ἦ","Ἧ":"ἧ","Ἰ":"ἰ","Ἱ":"ἱ","Ἲ":"ἲ","Ἳ":"ἳ","Ἴ":"ἴ","Ἵ":"ἵ","Ἶ":"ἶ","Ἷ":"ἷ","Ὀ":"ὀ","Ὁ":"ὁ","Ὂ":"ὂ","Ὃ":"ὃ","Ὄ":"ὄ","Ὅ":"ὅ","ὐ":"ὐ","ὒ":"ὒ","ὔ":"ὔ","ὖ":"ὖ","Ὑ":"ὑ","Ὓ":"ὓ","Ὕ":"ὕ","Ὗ":"ὗ","Ὠ":"ὠ","Ὡ":"ὡ","Ὢ":"ὢ","Ὣ":"ὣ","Ὤ":"ὤ","Ὥ":"ὥ","Ὦ":"ὦ","Ὧ":"ὧ","ᾀ":"ἀι","ᾁ":"ἁι","ᾂ":"ἂι","ᾃ":"ἃι","ᾄ":"ἄι","ᾅ":"ἅι","ᾆ":"ἆι","ᾇ":"ἇι","ᾈ":"ἀι","ᾉ":"ἁι","ᾊ":"ἂι","ᾋ":"ἃι","ᾌ":"ἄι","ᾍ":"ἅι","ᾎ":"ἆι","ᾏ":"ἇι","ᾐ":"ἠι","ᾑ":"ἡι","ᾒ":"ἢι","ᾓ":"ἣι","ᾔ":"ἤι","ᾕ":"ἥι","ᾖ":"ἦι","ᾗ":"ἧι","ᾘ":"ἠι","ᾙ":"ἡι","ᾚ":"ἢι","ᾛ":"ἣι","ᾜ":"ἤι","ᾝ":"ἥι","ᾞ":"ἦι","ᾟ":"ἧι","ᾠ":"ὠι","ᾡ":"ὡι","ᾢ":"ὢι","ᾣ":"ὣι","ᾤ":"ὤι","ᾥ":"ὥι","ᾦ":"ὦι","ᾧ":"ὧι","ᾨ":"ὠι","ᾩ":"ὡι","ᾪ":"ὢι","ᾫ":"ὣι","ᾬ":"ὤι","ᾭ":"ὥι","ᾮ":"ὦι","ᾯ":"ὧι","ᾲ":"ὰι","ᾳ":"αι","ᾴ":"άι","ᾶ":"ᾶ","ᾷ":"ᾶι","Ᾰ":"ᾰ","Ᾱ":"ᾱ","Ὰ":"ὰ","Ά":"ά","ᾼ":"αι","ι":"ι","ῂ":"ὴι","ῃ":"ηι","ῄ":"ήι","ῆ":"ῆ","ῇ":"ῆι","Ὲ":"ὲ","Έ":"έ","Ὴ":"ὴ","Ή":"ή","ῌ":"ηι","ῒ":"ῒ","ΐ":"ΐ","ῖ":"ῖ","ῗ":"ῗ","Ῐ":"ῐ","Ῑ":"ῑ","Ὶ":"ὶ","Ί":"ί","ῢ":"ῢ","ΰ":"ΰ","ῤ":"ῤ","ῦ":"ῦ","ῧ":"ῧ","Ῠ":"ῠ","Ῡ":"ῡ","Ὺ":"ὺ","Ύ":"ύ","Ῥ":"ῥ","ῲ":"ὼι","ῳ":"ωι","ῴ":"ώι","ῶ":"ῶ","ῷ":"ῶι","Ὸ":"ὸ","Ό":"ό","Ὼ":"ὼ","Ώ":"ώ","ῼ":"ωι","Ω":"ω","K":"k","Å":"å","Ⅎ":"ⅎ","Ⅰ":"ⅰ","Ⅱ":"ⅱ","Ⅲ":"ⅲ","Ⅳ":"ⅳ","Ⅴ":"ⅴ","Ⅵ":"ⅵ","Ⅶ":"ⅶ","Ⅷ":"ⅷ","Ⅸ":"ⅸ","Ⅹ":"ⅹ","Ⅺ":"ⅺ","Ⅻ":"ⅻ","Ⅼ":"ⅼ","Ⅽ":"ⅽ","Ⅾ":"ⅾ","Ⅿ":"ⅿ","Ↄ":"ↄ","Ⓐ":"ⓐ","Ⓑ":"ⓑ","Ⓒ":"ⓒ","Ⓓ":"ⓓ","Ⓔ":"ⓔ","Ⓕ":"ⓕ","Ⓖ":"ⓖ","Ⓗ":"ⓗ","Ⓘ":"ⓘ","Ⓙ":"ⓙ","Ⓚ":"ⓚ","Ⓛ":"ⓛ","Ⓜ":"ⓜ","Ⓝ":"ⓝ","Ⓞ":"ⓞ","Ⓟ":"ⓟ","Ⓠ":"ⓠ","Ⓡ":"ⓡ","Ⓢ":"ⓢ","Ⓣ":"ⓣ","Ⓤ":"ⓤ","Ⓥ":"ⓥ","Ⓦ":"ⓦ","Ⓧ":"ⓧ","Ⓨ":"ⓨ","Ⓩ":"ⓩ","Ⰰ":"ⰰ","Ⰱ":"ⰱ","Ⰲ":"ⰲ","Ⰳ":"ⰳ","Ⰴ":"ⰴ","Ⰵ":"ⰵ","Ⰶ":"ⰶ","Ⰷ":"ⰷ","Ⰸ":"ⰸ","Ⰹ":"ⰹ","Ⰺ":"ⰺ","Ⰻ":"ⰻ","Ⰼ":"ⰼ","Ⰽ":"ⰽ","Ⰾ":"ⰾ","Ⰿ":"ⰿ","Ⱀ":"ⱀ","Ⱁ":"ⱁ","Ⱂ":"ⱂ","Ⱃ":"ⱃ","Ⱄ":"ⱄ","Ⱅ":"ⱅ","Ⱆ":"ⱆ","Ⱇ":"ⱇ","Ⱈ":"ⱈ","Ⱉ":"ⱉ","Ⱊ":"ⱊ","Ⱋ":"ⱋ","Ⱌ":"ⱌ","Ⱍ":"ⱍ","Ⱎ":"ⱎ","Ⱏ":"ⱏ","Ⱐ":"ⱐ","Ⱑ":"ⱑ","Ⱒ":"ⱒ","Ⱓ":"ⱓ","Ⱔ":"ⱔ","Ⱕ":"ⱕ","Ⱖ":"ⱖ","Ⱗ":"ⱗ","Ⱘ":"ⱘ","Ⱙ":"ⱙ","Ⱚ":"ⱚ","Ⱛ":"ⱛ","Ⱜ":"ⱜ","Ⱝ":"ⱝ","Ⱞ":"ⱞ","Ⱟ":"ⱟ","Ⱡ":"ⱡ","Ɫ":"ɫ","Ᵽ":"ᵽ","Ɽ":"ɽ","Ⱨ":"ⱨ","Ⱪ":"ⱪ","Ⱬ":"ⱬ","Ɑ":"ɑ","Ɱ":"ɱ","Ɐ":"ɐ","Ɒ":"ɒ","Ⱳ":"ⱳ","Ⱶ":"ⱶ","Ȿ":"ȿ","Ɀ":"ɀ","Ⲁ":"ⲁ","Ⲃ":"ⲃ","Ⲅ":"ⲅ","Ⲇ":"ⲇ","Ⲉ":"ⲉ","Ⲋ":"ⲋ","Ⲍ":"ⲍ","Ⲏ":"ⲏ","Ⲑ":"ⲑ","Ⲓ":"ⲓ","Ⲕ":"ⲕ","Ⲗ":"ⲗ","Ⲙ":"ⲙ","Ⲛ":"ⲛ","Ⲝ":"ⲝ","Ⲟ":"ⲟ","Ⲡ":"ⲡ","Ⲣ":"ⲣ","Ⲥ":"ⲥ","Ⲧ":"ⲧ","Ⲩ":"ⲩ","Ⲫ":"ⲫ","Ⲭ":"ⲭ","Ⲯ":"ⲯ","Ⲱ":"ⲱ","Ⲳ":"ⲳ","Ⲵ":"ⲵ","Ⲷ":"ⲷ","Ⲹ":"ⲹ","Ⲻ":"ⲻ","Ⲽ":"ⲽ","Ⲿ":"ⲿ","Ⳁ":"ⳁ","Ⳃ":"ⳃ","Ⳅ":"ⳅ","Ⳇ":"ⳇ","Ⳉ":"ⳉ","Ⳋ":"ⳋ","Ⳍ":"ⳍ","Ⳏ":"ⳏ","Ⳑ":"ⳑ","Ⳓ":"ⳓ","Ⳕ":"ⳕ","Ⳗ":"ⳗ","Ⳙ":"ⳙ","Ⳛ":"ⳛ","Ⳝ":"ⳝ","Ⳟ":"ⳟ","Ⳡ":"ⳡ","Ⳣ":"ⳣ","Ⳬ":"ⳬ","Ⳮ":"ⳮ","Ⳳ":"ⳳ","Ꙁ":"ꙁ","Ꙃ":"ꙃ","Ꙅ":"ꙅ","Ꙇ":"ꙇ","Ꙉ":"ꙉ","Ꙋ":"ꙋ","Ꙍ":"ꙍ","Ꙏ":"ꙏ","Ꙑ":"ꙑ","Ꙓ":"ꙓ","Ꙕ":"ꙕ","Ꙗ":"ꙗ","Ꙙ":"ꙙ","Ꙛ":"ꙛ","Ꙝ":"ꙝ","Ꙟ":"ꙟ","Ꙡ":"ꙡ","Ꙣ":"ꙣ","Ꙥ":"ꙥ","Ꙧ":"ꙧ","Ꙩ":"ꙩ","Ꙫ":"ꙫ","Ꙭ":"ꙭ","Ꚁ":"ꚁ","Ꚃ":"ꚃ","Ꚅ":"ꚅ","Ꚇ":"ꚇ","Ꚉ":"ꚉ","Ꚋ":"ꚋ","Ꚍ":"ꚍ","Ꚏ":"ꚏ","Ꚑ":"ꚑ","Ꚓ":"ꚓ","Ꚕ":"ꚕ","Ꚗ":"ꚗ","Ꚙ":"ꚙ","Ꚛ":"ꚛ","Ꜣ":"ꜣ","Ꜥ":"ꜥ","Ꜧ":"ꜧ","Ꜩ":"ꜩ","Ꜫ":"ꜫ","Ꜭ":"ꜭ","Ꜯ":"ꜯ","Ꜳ":"ꜳ","Ꜵ":"ꜵ","Ꜷ":"ꜷ","Ꜹ":"ꜹ","Ꜻ":"ꜻ","Ꜽ":"ꜽ","Ꜿ":"ꜿ","Ꝁ":"ꝁ","Ꝃ":"ꝃ","Ꝅ":"ꝅ","Ꝇ":"ꝇ","Ꝉ":"ꝉ","Ꝋ":"ꝋ","Ꝍ":"ꝍ","Ꝏ":"ꝏ","Ꝑ":"ꝑ","Ꝓ":"ꝓ","Ꝕ":"ꝕ","Ꝗ":"ꝗ","Ꝙ":"ꝙ","Ꝛ":"ꝛ","Ꝝ":"ꝝ","Ꝟ":"ꝟ","Ꝡ":"ꝡ","Ꝣ":"ꝣ","Ꝥ":"ꝥ","Ꝧ":"ꝧ","Ꝩ":"ꝩ","Ꝫ":"ꝫ","Ꝭ":"ꝭ","Ꝯ":"ꝯ","Ꝺ":"ꝺ","Ꝼ":"ꝼ","Ᵹ":"ᵹ","Ꝿ":"ꝿ","Ꞁ":"ꞁ","Ꞃ":"ꞃ","Ꞅ":"ꞅ","Ꞇ":"ꞇ","Ꞌ":"ꞌ","Ɥ":"ɥ","Ꞑ":"ꞑ","Ꞓ":"ꞓ","Ꞗ":"ꞗ","Ꞙ":"ꞙ","Ꞛ":"ꞛ","Ꞝ":"ꞝ","Ꞟ":"ꞟ","Ꞡ":"ꞡ","Ꞣ":"ꞣ","Ꞥ":"ꞥ","Ꞧ":"ꞧ","Ꞩ":"ꞩ","Ɦ":"ɦ","Ɜ":"ɜ","Ɡ":"ɡ","Ɬ":"ɬ","Ɪ":"ɪ","Ʞ":"ʞ","Ʇ":"ʇ","Ʝ":"ʝ","Ꭓ":"ꭓ","Ꞵ":"ꞵ","Ꞷ":"ꞷ","Ꞹ":"ꞹ","Ꞻ":"ꞻ","Ꞽ":"ꞽ","Ꞿ":"ꞿ","Ꟁ":"ꟁ","Ꟃ":"ꟃ","Ꞔ":"ꞔ","Ʂ":"ʂ","Ᶎ":"ᶎ","Ꟈ":"ꟈ","Ꟊ":"ꟊ","Ɤ":"ɤ","Ꟍ":"ꟍ","꟎":"꟏","Ꟑ":"ꟑ","꟒":"ꟓ","꟔":"ꟕ","Ꟗ":"ꟗ","Ꟙ":"ꟙ","Ꟛ":"ꟛ","Ƛ":"ƛ","Ꟶ":"ꟶ","ꭰ":"Ꭰ","ꭱ":"Ꭱ","ꭲ":"Ꭲ","ꭳ":"Ꭳ","ꭴ":"Ꭴ","ꭵ":"Ꭵ","ꭶ":"Ꭶ","ꭷ":"Ꭷ","ꭸ":"Ꭸ","ꭹ":"Ꭹ","ꭺ":"Ꭺ","ꭻ":"Ꭻ","ꭼ":"Ꭼ","ꭽ":"Ꭽ","ꭾ":"Ꭾ","ꭿ":"Ꭿ","ꮀ":"Ꮀ","ꮁ":"Ꮁ","ꮂ":"Ꮂ","ꮃ":"Ꮃ","ꮄ":"Ꮄ","ꮅ":"Ꮅ","ꮆ":"Ꮆ","ꮇ":"Ꮇ","ꮈ":"Ꮈ","ꮉ":"Ꮉ","ꮊ":"Ꮊ","ꮋ":"Ꮋ","ꮌ":"Ꮌ","ꮍ":"Ꮍ","ꮎ":"Ꮎ","ꮏ":"Ꮏ","ꮐ":"Ꮐ","ꮑ":"Ꮑ","ꮒ":"Ꮒ","ꮓ":"Ꮓ","ꮔ":"Ꮔ","ꮕ":"Ꮕ","ꮖ":"Ꮖ","ꮗ":"Ꮗ","ꮘ":"Ꮘ","ꮙ":"Ꮙ","ꮚ":"Ꮚ","ꮛ":"Ꮛ","ꮜ":"Ꮜ","ꮝ":"Ꮝ","ꮞ":"Ꮞ","ꮟ":"Ꮟ","ꮠ":"Ꮠ","ꮡ":"Ꮡ","ꮢ":"Ꮢ","ꮣ":"Ꮣ","ꮤ":"Ꮤ","ꮥ":"Ꮥ","ꮦ":"Ꮦ","ꮧ":"Ꮧ","ꮨ":"Ꮨ","ꮩ":"Ꮩ","ꮪ":"Ꮪ","ꮫ":"Ꮫ","ꮬ":"Ꮬ","ꮭ":"Ꮭ","ꮮ":"Ꮮ","ꮯ":"Ꮯ","ꮰ":"Ꮰ","ꮱ":"Ꮱ","ꮲ":"Ꮲ","ꮳ":"Ꮳ","ꮴ":"Ꮴ","ꮵ":"Ꮵ","ꮶ":"Ꮶ","ꮷ":"Ꮷ","ꮸ":"Ꮸ","ꮹ":"Ꮹ","ꮺ":"Ꮺ","ꮻ":"Ꮻ","ꮼ":"Ꮼ","ꮽ":"Ꮽ","ꮾ":"Ꮾ","ꮿ":"Ꮿ","ff":"ff","fi":"fi","fl":"fl","ffi":"ffi","ffl":"ffl","ſt":"st","st":"st","ﬓ":"մն","ﬔ":"մե","ﬕ":"մի","ﬖ":"վն","ﬗ":"մխ","A":"a","B":"b","C":"c","D":"d","E":"e","F":"f","G":"g","H":"h","I":"i","J":"j","K":"k","L":"l","M":"m","N":"n","O":"o","P":"p","Q":"q","R":"r","S":"s","T":"t","U":"u","V":"v","W":"w","X":"x","Y":"y","Z":"z","𐐀":"𐐨","𐐁":"𐐩","𐐂":"𐐪","𐐃":"𐐫","𐐄":"𐐬","𐐅":"𐐭","𐐆":"𐐮","𐐇":"𐐯","𐐈":"𐐰","𐐉":"𐐱","𐐊":"𐐲","𐐋":"𐐳","𐐌":"𐐴","𐐍":"𐐵","𐐎":"𐐶","𐐏":"𐐷","𐐐":"𐐸","𐐑":"𐐹","𐐒":"𐐺","𐐓":"𐐻","𐐔":"𐐼","𐐕":"𐐽","𐐖":"𐐾","𐐗":"𐐿","𐐘":"𐑀","𐐙":"𐑁","𐐚":"𐑂","𐐛":"𐑃","𐐜":"𐑄","𐐝":"𐑅","𐐞":"𐑆","𐐟":"𐑇","𐐠":"𐑈","𐐡":"𐑉","𐐢":"𐑊","𐐣":"𐑋","𐐤":"𐑌","𐐥":"𐑍","𐐦":"𐑎","𐐧":"𐑏","𐒰":"𐓘","𐒱":"𐓙","𐒲":"𐓚","𐒳":"𐓛","𐒴":"𐓜","𐒵":"𐓝","𐒶":"𐓞","𐒷":"𐓟","𐒸":"𐓠","𐒹":"𐓡","𐒺":"𐓢","𐒻":"𐓣","𐒼":"𐓤","𐒽":"𐓥","𐒾":"𐓦","𐒿":"𐓧","𐓀":"𐓨","𐓁":"𐓩","𐓂":"𐓪","𐓃":"𐓫","𐓄":"𐓬","𐓅":"𐓭","𐓆":"𐓮","𐓇":"𐓯","𐓈":"𐓰","𐓉":"𐓱","𐓊":"𐓲","𐓋":"𐓳","𐓌":"𐓴","𐓍":"𐓵","𐓎":"𐓶","𐓏":"𐓷","𐓐":"𐓸","𐓑":"𐓹","𐓒":"𐓺","𐓓":"𐓻","𐕰":"𐖗","𐕱":"𐖘","𐕲":"𐖙","𐕳":"𐖚","𐕴":"𐖛","𐕵":"𐖜","𐕶":"𐖝","𐕷":"𐖞","𐕸":"𐖟","𐕹":"𐖠","𐕺":"𐖡","𐕼":"𐖣","𐕽":"𐖤","𐕾":"𐖥","𐕿":"𐖦","𐖀":"𐖧","𐖁":"𐖨","𐖂":"𐖩","𐖃":"𐖪","𐖄":"𐖫","𐖅":"𐖬","𐖆":"𐖭","𐖇":"𐖮","𐖈":"𐖯","𐖉":"𐖰","𐖊":"𐖱","𐖌":"𐖳","𐖍":"𐖴","𐖎":"𐖵","𐖏":"𐖶","𐖐":"𐖷","𐖑":"𐖸","𐖒":"𐖹","𐖔":"𐖻","𐖕":"𐖼","𐲀":"𐳀","𐲁":"𐳁","𐲂":"𐳂","𐲃":"𐳃","𐲄":"𐳄","𐲅":"𐳅","𐲆":"𐳆","𐲇":"𐳇","𐲈":"𐳈","𐲉":"𐳉","𐲊":"𐳊","𐲋":"𐳋","𐲌":"𐳌","𐲍":"𐳍","𐲎":"𐳎","𐲏":"𐳏","𐲐":"𐳐","𐲑":"𐳑","𐲒":"𐳒","𐲓":"𐳓","𐲔":"𐳔","𐲕":"𐳕","𐲖":"𐳖","𐲗":"𐳗","𐲘":"𐳘","𐲙":"𐳙","𐲚":"𐳚","𐲛":"𐳛","𐲜":"𐳜","𐲝":"𐳝","𐲞":"𐳞","𐲟":"𐳟","𐲠":"𐳠","𐲡":"𐳡","𐲢":"𐳢","𐲣":"𐳣","𐲤":"𐳤","𐲥":"𐳥","𐲦":"𐳦","𐲧":"𐳧","𐲨":"𐳨","𐲩":"𐳩","𐲪":"𐳪","𐲫":"𐳫","𐲬":"𐳬","𐲭":"𐳭","𐲮":"𐳮","𐲯":"𐳯","𐲰":"𐳰","𐲱":"𐳱","𐲲":"𐳲","𐵐":"𐵰","𐵑":"𐵱","𐵒":"𐵲","𐵓":"𐵳","𐵔":"𐵴","𐵕":"𐵵","𐵖":"𐵶","𐵗":"𐵷","𐵘":"𐵸","𐵙":"𐵹","𐵚":"𐵺","𐵛":"𐵻","𐵜":"𐵼","𐵝":"𐵽","𐵞":"𐵾","𐵟":"𐵿","𐵠":"𐶀","𐵡":"𐶁","𐵢":"𐶂","𐵣":"𐶃","𐵤":"𐶄","𐵥":"𐶅","𑢠":"𑣀","𑢡":"𑣁","𑢢":"𑣂","𑢣":"𑣃","𑢤":"𑣄","𑢥":"𑣅","𑢦":"𑣆","𑢧":"𑣇","𑢨":"𑣈","𑢩":"𑣉","𑢪":"𑣊","𑢫":"𑣋","𑢬":"𑣌","𑢭":"𑣍","𑢮":"𑣎","𑢯":"𑣏","𑢰":"𑣐","𑢱":"𑣑","𑢲":"𑣒","𑢳":"𑣓","𑢴":"𑣔","𑢵":"𑣕","𑢶":"𑣖","𑢷":"𑣗","𑢸":"𑣘","𑢹":"𑣙","𑢺":"𑣚","𑢻":"𑣛","𑢼":"𑣜","𑢽":"𑣝","𑢾":"𑣞","𑢿":"𑣟","𖹀":"𖹠","𖹁":"𖹡","𖹂":"𖹢","𖹃":"𖹣","𖹄":"𖹤","𖹅":"𖹥","𖹆":"𖹦","𖹇":"𖹧","𖹈":"𖹨","𖹉":"𖹩","𖹊":"𖹪","𖹋":"𖹫","𖹌":"𖹬","𖹍":"𖹭","𖹎":"𖹮","𖹏":"𖹯","𖹐":"𖹰","𖹑":"𖹱","𖹒":"𖹲","𖹓":"𖹳","𖹔":"𖹴","𖹕":"𖹵","𖹖":"𖹶","𖹗":"𖹷","𖹘":"𖹸","𖹙":"𖹹","𖹚":"𖹺","𖹛":"𖹻","𖹜":"𖹼","𖹝":"𖹽","𖹞":"𖹾","𖹟":"𖹿","𖺠":"𖺻","𖺡":"𖺼","𖺢":"𖺽","𖺣":"𖺾","𖺤":"𖺿","𖺥":"𖻀","𖺦":"𖻁","𖺧":"𖻂","𖺨":"𖻃","𖺩":"𖻄","𖺪":"𖻅","𖺫":"𖻆","𖺬":"𖻇","𖺭":"𖻈","𖺮":"𖻉","𖺯":"𖻊","𖺰":"𖻋","𖺱":"𖻌","𖺲":"𖻍","𖺳":"𖻎","𖺴":"𖻏","𖺵":"𖻐","𖺶":"𖻑","𖺷":"𖻒","𖺸":"𖻓","𞤀":"𞤢","𞤁":"𞤣","𞤂":"𞤤","𞤃":"𞤥","𞤄":"𞤦","𞤅":"𞤧","𞤆":"𞤨","𞤇":"𞤩","𞤈":"𞤪","𞤉":"𞤫","𞤊":"𞤬","𞤋":"𞤭","𞤌":"𞤮","𞤍":"𞤯","𞤎":"𞤰","𞤏":"𞤱","𞤐":"𞤲","𞤑":"𞤳","𞤒":"𞤴","𞤓":"𞤵","𞤔":"𞤶","𞤕":"𞤷","𞤖":"𞤸","𞤗":"𞤹","𞤘":"𞤺","𞤙":"𞤻","𞤚":"𞤼","𞤛":"𞤽","𞤜":"𞤾","𞤝":"𞤿","𞤞":"𞥀","𞤟":"𞥁","𞤠":"𞥂","𞤡":"𞥃"}} diff --git a/rust/assets/catalog.json b/rust/assets/catalog.json new file mode 100644 index 00000000..d037e0b2 --- /dev/null +++ b/rust/assets/catalog.json @@ -0,0 +1 @@ +{"base_families":["batch.basic","bounty.basic","cert.request","communication.basic","delivery.basic","discovery.basic","discussion.basic","git.basic","group.basic","hosting.basic","identity.basic","keystore.basic","money.basic","orders.basic","resource.basic","sharing.basic","store.basic","transfer.basic"],"base_grants":[{"capability":"batch.basic","constraints":{},"operations":["batch.atomic@1","batch.independent@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"bounty.basic","constraints":{},"operations":["bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"cert.request","constraints":{},"operations":["cert.cancel@1","cert.request@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"communication.basic","constraints":{},"operations":["communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"delivery.basic","constraints":{},"operations":["delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"discovery.basic","constraints":{},"operations":["achievement.list@1","cert.get@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","job.get@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"discussion.basic","constraints":{},"operations":["discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"git.basic","constraints":{},"operations":["git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"group.basic","constraints":{},"operations":["group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"hosting.basic","constraints":{},"operations":["hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"identity.basic","constraints":{},"operations":["achievement.answer@1","achievement.finish@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_close@1","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.personal_put@1","identity.personal_put@2","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"keystore.basic","constraints":{},"operations":["keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"money.basic","constraints":{},"operations":["money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"orders.basic","constraints":{},"operations":["orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.basic","constraints":{},"operations":["content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"sharing.basic","constraints":{},"operations":["sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"store.basic","constraints":{},"operations":["store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"transfer.basic","constraints":{},"operations":["query.save@1","query.saved_archive@1","query.saved_get@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1}],"capabilities":[{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"batch.basic","operations":["batch.atomic@1","batch.independent@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"bounty.basic","operations":["bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":true,"constraints_schema":null,"delegatable":false,"name":"cert.ca.issue","operations":["cert.publish@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":true,"constraints_schema":null,"delegatable":true,"name":"cert.issue","operations":["cert.get@1","cert.publish@1","discovery.get@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"cert.request","operations":["cert.cancel@1","cert.request@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":true,"constraints_schema":null,"delegatable":true,"name":"cert.revoke","operations":["cert.revoke@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"communication.basic","operations":["communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"delivery.basic","operations":["delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"discovery.basic","operations":["achievement.list@1","cert.get@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","job.get@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"discussion.basic","operations":["discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"git.basic","operations":["git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"group.basic","operations":["group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"group.manage_override","operations":["group.admin.add@1","group.admin.remove@1","group.approve@1","group.invite@1","group.member.add@1","group.member.remove@1","group.reject@1","group.remove@1"],"replaces_checks":["manage"],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"hosting.basic","operations":["hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"identity.basic","operations":["achievement.answer@1","achievement.finish@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_close@1","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"identity.recover","operations":["identity.recover@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"keystore.basic","operations":["keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"money.basic","operations":["money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"orders.basic","operations":["orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"resource.basic","operations":["content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"resource.certified_write","operations":["achievement.answer@1","achievement.finish@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.close@1","bounty.create@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.checkpoint_create@1","communication.claim_create@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.follow@1","communication.game_join_ticket@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_withdraw@1","communication.presence_clear@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.send@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_subscribe@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.notify@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discussion.ack@1","discussion.bookmark@1","discussion.fork@1","discussion.fork@2","discussion.like@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_prove@1","discussion.reply@1","discussion.reply@2","discussion.repost@1","discussion.repost@2","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.mkdir@1","file.move@1","file.patch@1","file.write@1","git.create@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.push@1","git.receive@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.owner_transfer@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegation_revoke@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_rotate@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_put@1","identity.legacy_put@2","identity.link_claim@1","identity.link_close@1","identity.link_open@1","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_envelope_register@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","keystore.put@1","keystore.put@2","money.purchase_cancel@1","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_execute@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","query.save@1","query.saved_archive@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_revoke@1","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_update@1","store.package_deposit@1","system.config@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_put@1","transfer.query_seal@1","transfer.seal@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"resource.chgrp_override","operations":["content.chgrp@1"],"replaces_checks":["chgrp"],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"resource.chmod_override","operations":["content.chmod@1"],"replaces_checks":["chmod"],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"resource.chown","operations":["content.chown@1"],"replaces_checks":["chown"],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"resource.purge","operations":["content.purge@1"],"replaces_checks":["purge"],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"resource.read_override","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"replaces_checks":["list","read","traverse"],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"resource.write_override","operations":["achievement.answer@1","achievement.finish@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.close@1","bounty.create@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.checkpoint_create@1","communication.claim_create@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.follow@1","communication.game_join_ticket@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_withdraw@1","communication.presence_clear@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.send@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_subscribe@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.notify@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discussion.ack@1","discussion.bookmark@1","discussion.fork@1","discussion.fork@2","discussion.like@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_prove@1","discussion.reply@1","discussion.reply@2","discussion.repost@1","discussion.repost@2","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.mkdir@1","file.move@1","file.patch@1","file.write@1","git.create@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.push@1","git.receive@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.owner_transfer@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegation_revoke@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_rotate@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_put@1","identity.legacy_put@2","identity.link_claim@1","identity.link_close@1","identity.link_open@1","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_envelope_register@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","keystore.put@1","keystore.put@2","money.purchase_cancel@1","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_execute@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","query.save@1","query.saved_archive@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_revoke@1","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_update@1","store.package_deposit@1","system.config@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_put@1","transfer.query_seal@1","transfer.seal@1"],"replaces_checks":["write"],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"sharing.basic","operations":["sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"store.basic","operations":["store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"system.config","operations":["system.config@1","system.share_links_set@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"system.inspect","operations":["system.inspect@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"system.maintenance","operations":["system.maintenance@1","system.maintenance@2"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"system.namespace","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":{"id":"schema:network-constraints","revision":null},"delegatable":true,"name":"tool.net.private","operations":["tool.run@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":{"id":"schema:network-constraints","revision":null},"delegatable":true,"name":"tool.use","operations":["discovery.get@1","discovery.list@1","discovery.raw@1","job.get@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.seal@1","transfer.status@1"],"replaces_checks":["tool_use"],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"transfer.basic","operations":["query.save@1","query.saved_archive@1","query.saved_get@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1},{"ca_only":false,"constraints_schema":null,"delegatable":true,"name":"webhook.domain","operations":["communication.webhook_subscribe@1"],"replaces_checks":[],"scope_types":["attachment","certificate","checkpoint","claim","collab_offer","collab_proposal","collab_request","csr","delegation","file","keystore","legacy_directive","listing","order","order_collection","organization","post","repo","saved_query","skill","template","todo","tool","topic","user","watch","website"],"version":1}],"casefold":{"A":"a","B":"b","C":"c","D":"d","E":"e","F":"f","G":"g","H":"h","I":"i","J":"j","K":"k","L":"l","M":"m","N":"n","O":"o","P":"p","Q":"q","R":"r","S":"s","T":"t","U":"u","V":"v","W":"w","X":"x","Y":"y","Z":"z","µ":"μ","À":"à","Á":"á","Â":"â","Ã":"ã","Ä":"ä","Å":"å","Æ":"æ","Ç":"ç","È":"è","É":"é","Ê":"ê","Ë":"ë","Ì":"ì","Í":"í","Î":"î","Ï":"ï","Ð":"ð","Ñ":"ñ","Ò":"ò","Ó":"ó","Ô":"ô","Õ":"õ","Ö":"ö","Ø":"ø","Ù":"ù","Ú":"ú","Û":"û","Ü":"ü","Ý":"ý","Þ":"þ","ß":"ss","Ā":"ā","Ă":"ă","Ą":"ą","Ć":"ć","Ĉ":"ĉ","Ċ":"ċ","Č":"č","Ď":"ď","Đ":"đ","Ē":"ē","Ĕ":"ĕ","Ė":"ė","Ę":"ę","Ě":"ě","Ĝ":"ĝ","Ğ":"ğ","Ġ":"ġ","Ģ":"ģ","Ĥ":"ĥ","Ħ":"ħ","Ĩ":"ĩ","Ī":"ī","Ĭ":"ĭ","Į":"į","İ":"i̇","IJ":"ij","Ĵ":"ĵ","Ķ":"ķ","Ĺ":"ĺ","Ļ":"ļ","Ľ":"ľ","Ŀ":"ŀ","Ł":"ł","Ń":"ń","Ņ":"ņ","Ň":"ň","ʼn":"ʼn","Ŋ":"ŋ","Ō":"ō","Ŏ":"ŏ","Ő":"ő","Œ":"œ","Ŕ":"ŕ","Ŗ":"ŗ","Ř":"ř","Ś":"ś","Ŝ":"ŝ","Ş":"ş","Š":"š","Ţ":"ţ","Ť":"ť","Ŧ":"ŧ","Ũ":"ũ","Ū":"ū","Ŭ":"ŭ","Ů":"ů","Ű":"ű","Ų":"ų","Ŵ":"ŵ","Ŷ":"ŷ","Ÿ":"ÿ","Ź":"ź","Ż":"ż","Ž":"ž","ſ":"s","Ɓ":"ɓ","Ƃ":"ƃ","Ƅ":"ƅ","Ɔ":"ɔ","Ƈ":"ƈ","Ɖ":"ɖ","Ɗ":"ɗ","Ƌ":"ƌ","Ǝ":"ǝ","Ə":"ə","Ɛ":"ɛ","Ƒ":"ƒ","Ɠ":"ɠ","Ɣ":"ɣ","Ɩ":"ɩ","Ɨ":"ɨ","Ƙ":"ƙ","Ɯ":"ɯ","Ɲ":"ɲ","Ɵ":"ɵ","Ơ":"ơ","Ƣ":"ƣ","Ƥ":"ƥ","Ʀ":"ʀ","Ƨ":"ƨ","Ʃ":"ʃ","Ƭ":"ƭ","Ʈ":"ʈ","Ư":"ư","Ʊ":"ʊ","Ʋ":"ʋ","Ƴ":"ƴ","Ƶ":"ƶ","Ʒ":"ʒ","Ƹ":"ƹ","Ƽ":"ƽ","DŽ":"dž","Dž":"dž","LJ":"lj","Lj":"lj","NJ":"nj","Nj":"nj","Ǎ":"ǎ","Ǐ":"ǐ","Ǒ":"ǒ","Ǔ":"ǔ","Ǖ":"ǖ","Ǘ":"ǘ","Ǚ":"ǚ","Ǜ":"ǜ","Ǟ":"ǟ","Ǡ":"ǡ","Ǣ":"ǣ","Ǥ":"ǥ","Ǧ":"ǧ","Ǩ":"ǩ","Ǫ":"ǫ","Ǭ":"ǭ","Ǯ":"ǯ","ǰ":"ǰ","DZ":"dz","Dz":"dz","Ǵ":"ǵ","Ƕ":"ƕ","Ƿ":"ƿ","Ǹ":"ǹ","Ǻ":"ǻ","Ǽ":"ǽ","Ǿ":"ǿ","Ȁ":"ȁ","Ȃ":"ȃ","Ȅ":"ȅ","Ȇ":"ȇ","Ȉ":"ȉ","Ȋ":"ȋ","Ȍ":"ȍ","Ȏ":"ȏ","Ȑ":"ȑ","Ȓ":"ȓ","Ȕ":"ȕ","Ȗ":"ȗ","Ș":"ș","Ț":"ț","Ȝ":"ȝ","Ȟ":"ȟ","Ƞ":"ƞ","Ȣ":"ȣ","Ȥ":"ȥ","Ȧ":"ȧ","Ȩ":"ȩ","Ȫ":"ȫ","Ȭ":"ȭ","Ȯ":"ȯ","Ȱ":"ȱ","Ȳ":"ȳ","Ⱥ":"ⱥ","Ȼ":"ȼ","Ƚ":"ƚ","Ⱦ":"ⱦ","Ɂ":"ɂ","Ƀ":"ƀ","Ʉ":"ʉ","Ʌ":"ʌ","Ɇ":"ɇ","Ɉ":"ɉ","Ɋ":"ɋ","Ɍ":"ɍ","Ɏ":"ɏ","ͅ":"ι","Ͱ":"ͱ","Ͳ":"ͳ","Ͷ":"ͷ","Ϳ":"ϳ","Ά":"ά","Έ":"έ","Ή":"ή","Ί":"ί","Ό":"ό","Ύ":"ύ","Ώ":"ώ","ΐ":"ΐ","Α":"α","Β":"β","Γ":"γ","Δ":"δ","Ε":"ε","Ζ":"ζ","Η":"η","Θ":"θ","Ι":"ι","Κ":"κ","Λ":"λ","Μ":"μ","Ν":"ν","Ξ":"ξ","Ο":"ο","Π":"π","Ρ":"ρ","Σ":"σ","Τ":"τ","Υ":"υ","Φ":"φ","Χ":"χ","Ψ":"ψ","Ω":"ω","Ϊ":"ϊ","Ϋ":"ϋ","ΰ":"ΰ","ς":"σ","Ϗ":"ϗ","ϐ":"β","ϑ":"θ","ϕ":"φ","ϖ":"π","Ϙ":"ϙ","Ϛ":"ϛ","Ϝ":"ϝ","Ϟ":"ϟ","Ϡ":"ϡ","Ϣ":"ϣ","Ϥ":"ϥ","Ϧ":"ϧ","Ϩ":"ϩ","Ϫ":"ϫ","Ϭ":"ϭ","Ϯ":"ϯ","ϰ":"κ","ϱ":"ρ","ϴ":"θ","ϵ":"ε","Ϸ":"ϸ","Ϲ":"ϲ","Ϻ":"ϻ","Ͻ":"ͻ","Ͼ":"ͼ","Ͽ":"ͽ","Ѐ":"ѐ","Ё":"ё","Ђ":"ђ","Ѓ":"ѓ","Є":"є","Ѕ":"ѕ","І":"і","Ї":"ї","Ј":"ј","Љ":"љ","Њ":"њ","Ћ":"ћ","Ќ":"ќ","Ѝ":"ѝ","Ў":"ў","Џ":"џ","А":"а","Б":"б","В":"в","Г":"г","Д":"д","Е":"е","Ж":"ж","З":"з","И":"и","Й":"й","К":"к","Л":"л","М":"м","Н":"н","О":"о","П":"п","Р":"р","С":"с","Т":"т","У":"у","Ф":"ф","Х":"х","Ц":"ц","Ч":"ч","Ш":"ш","Щ":"щ","Ъ":"ъ","Ы":"ы","Ь":"ь","Э":"э","Ю":"ю","Я":"я","Ѡ":"ѡ","Ѣ":"ѣ","Ѥ":"ѥ","Ѧ":"ѧ","Ѩ":"ѩ","Ѫ":"ѫ","Ѭ":"ѭ","Ѯ":"ѯ","Ѱ":"ѱ","Ѳ":"ѳ","Ѵ":"ѵ","Ѷ":"ѷ","Ѹ":"ѹ","Ѻ":"ѻ","Ѽ":"ѽ","Ѿ":"ѿ","Ҁ":"ҁ","Ҋ":"ҋ","Ҍ":"ҍ","Ҏ":"ҏ","Ґ":"ґ","Ғ":"ғ","Ҕ":"ҕ","Җ":"җ","Ҙ":"ҙ","Қ":"қ","Ҝ":"ҝ","Ҟ":"ҟ","Ҡ":"ҡ","Ң":"ң","Ҥ":"ҥ","Ҧ":"ҧ","Ҩ":"ҩ","Ҫ":"ҫ","Ҭ":"ҭ","Ү":"ү","Ұ":"ұ","Ҳ":"ҳ","Ҵ":"ҵ","Ҷ":"ҷ","Ҹ":"ҹ","Һ":"һ","Ҽ":"ҽ","Ҿ":"ҿ","Ӏ":"ӏ","Ӂ":"ӂ","Ӄ":"ӄ","Ӆ":"ӆ","Ӈ":"ӈ","Ӊ":"ӊ","Ӌ":"ӌ","Ӎ":"ӎ","Ӑ":"ӑ","Ӓ":"ӓ","Ӕ":"ӕ","Ӗ":"ӗ","Ә":"ә","Ӛ":"ӛ","Ӝ":"ӝ","Ӟ":"ӟ","Ӡ":"ӡ","Ӣ":"ӣ","Ӥ":"ӥ","Ӧ":"ӧ","Ө":"ө","Ӫ":"ӫ","Ӭ":"ӭ","Ӯ":"ӯ","Ӱ":"ӱ","Ӳ":"ӳ","Ӵ":"ӵ","Ӷ":"ӷ","Ӹ":"ӹ","Ӻ":"ӻ","Ӽ":"ӽ","Ӿ":"ӿ","Ԁ":"ԁ","Ԃ":"ԃ","Ԅ":"ԅ","Ԇ":"ԇ","Ԉ":"ԉ","Ԋ":"ԋ","Ԍ":"ԍ","Ԏ":"ԏ","Ԑ":"ԑ","Ԓ":"ԓ","Ԕ":"ԕ","Ԗ":"ԗ","Ԙ":"ԙ","Ԛ":"ԛ","Ԝ":"ԝ","Ԟ":"ԟ","Ԡ":"ԡ","Ԣ":"ԣ","Ԥ":"ԥ","Ԧ":"ԧ","Ԩ":"ԩ","Ԫ":"ԫ","Ԭ":"ԭ","Ԯ":"ԯ","Ա":"ա","Բ":"բ","Գ":"գ","Դ":"դ","Ե":"ե","Զ":"զ","Է":"է","Ը":"ը","Թ":"թ","Ժ":"ժ","Ի":"ի","Լ":"լ","Խ":"խ","Ծ":"ծ","Կ":"կ","Հ":"հ","Ձ":"ձ","Ղ":"ղ","Ճ":"ճ","Մ":"մ","Յ":"յ","Ն":"ն","Շ":"շ","Ո":"ո","Չ":"չ","Պ":"պ","Ջ":"ջ","Ռ":"ռ","Ս":"ս","Վ":"վ","Տ":"տ","Ր":"ր","Ց":"ց","Ւ":"ւ","Փ":"փ","Ք":"ք","Օ":"օ","Ֆ":"ֆ","և":"եւ","Ⴀ":"ⴀ","Ⴁ":"ⴁ","Ⴂ":"ⴂ","Ⴃ":"ⴃ","Ⴄ":"ⴄ","Ⴅ":"ⴅ","Ⴆ":"ⴆ","Ⴇ":"ⴇ","Ⴈ":"ⴈ","Ⴉ":"ⴉ","Ⴊ":"ⴊ","Ⴋ":"ⴋ","Ⴌ":"ⴌ","Ⴍ":"ⴍ","Ⴎ":"ⴎ","Ⴏ":"ⴏ","Ⴐ":"ⴐ","Ⴑ":"ⴑ","Ⴒ":"ⴒ","Ⴓ":"ⴓ","Ⴔ":"ⴔ","Ⴕ":"ⴕ","Ⴖ":"ⴖ","Ⴗ":"ⴗ","Ⴘ":"ⴘ","Ⴙ":"ⴙ","Ⴚ":"ⴚ","Ⴛ":"ⴛ","Ⴜ":"ⴜ","Ⴝ":"ⴝ","Ⴞ":"ⴞ","Ⴟ":"ⴟ","Ⴠ":"ⴠ","Ⴡ":"ⴡ","Ⴢ":"ⴢ","Ⴣ":"ⴣ","Ⴤ":"ⴤ","Ⴥ":"ⴥ","Ⴧ":"ⴧ","Ⴭ":"ⴭ","ᏸ":"Ᏸ","ᏹ":"Ᏹ","ᏺ":"Ᏺ","ᏻ":"Ᏻ","ᏼ":"Ᏼ","ᏽ":"Ᏽ","ᲀ":"в","ᲁ":"д","ᲂ":"о","ᲃ":"с","ᲄ":"т","ᲅ":"т","ᲆ":"ъ","ᲇ":"ѣ","ᲈ":"ꙋ","Ᲊ":"ᲊ","Ა":"ა","Ბ":"ბ","Გ":"გ","Დ":"დ","Ე":"ე","Ვ":"ვ","Ზ":"ზ","Თ":"თ","Ი":"ი","Კ":"კ","Ლ":"ლ","Მ":"მ","Ნ":"ნ","Ო":"ო","Პ":"პ","Ჟ":"ჟ","Რ":"რ","Ს":"ს","Ტ":"ტ","Უ":"უ","Ფ":"ფ","Ქ":"ქ","Ღ":"ღ","Ყ":"ყ","Შ":"შ","Ჩ":"ჩ","Ც":"ც","Ძ":"ძ","Წ":"წ","Ჭ":"ჭ","Ხ":"ხ","Ჯ":"ჯ","Ჰ":"ჰ","Ჱ":"ჱ","Ჲ":"ჲ","Ჳ":"ჳ","Ჴ":"ჴ","Ჵ":"ჵ","Ჶ":"ჶ","Ჷ":"ჷ","Ჸ":"ჸ","Ჹ":"ჹ","Ჺ":"ჺ","Ჽ":"ჽ","Ჾ":"ჾ","Ჿ":"ჿ","Ḁ":"ḁ","Ḃ":"ḃ","Ḅ":"ḅ","Ḇ":"ḇ","Ḉ":"ḉ","Ḋ":"ḋ","Ḍ":"ḍ","Ḏ":"ḏ","Ḑ":"ḑ","Ḓ":"ḓ","Ḕ":"ḕ","Ḗ":"ḗ","Ḙ":"ḙ","Ḛ":"ḛ","Ḝ":"ḝ","Ḟ":"ḟ","Ḡ":"ḡ","Ḣ":"ḣ","Ḥ":"ḥ","Ḧ":"ḧ","Ḩ":"ḩ","Ḫ":"ḫ","Ḭ":"ḭ","Ḯ":"ḯ","Ḱ":"ḱ","Ḳ":"ḳ","Ḵ":"ḵ","Ḷ":"ḷ","Ḹ":"ḹ","Ḻ":"ḻ","Ḽ":"ḽ","Ḿ":"ḿ","Ṁ":"ṁ","Ṃ":"ṃ","Ṅ":"ṅ","Ṇ":"ṇ","Ṉ":"ṉ","Ṋ":"ṋ","Ṍ":"ṍ","Ṏ":"ṏ","Ṑ":"ṑ","Ṓ":"ṓ","Ṕ":"ṕ","Ṗ":"ṗ","Ṙ":"ṙ","Ṛ":"ṛ","Ṝ":"ṝ","Ṟ":"ṟ","Ṡ":"ṡ","Ṣ":"ṣ","Ṥ":"ṥ","Ṧ":"ṧ","Ṩ":"ṩ","Ṫ":"ṫ","Ṭ":"ṭ","Ṯ":"ṯ","Ṱ":"ṱ","Ṳ":"ṳ","Ṵ":"ṵ","Ṷ":"ṷ","Ṹ":"ṹ","Ṻ":"ṻ","Ṽ":"ṽ","Ṿ":"ṿ","Ẁ":"ẁ","Ẃ":"ẃ","Ẅ":"ẅ","Ẇ":"ẇ","Ẉ":"ẉ","Ẋ":"ẋ","Ẍ":"ẍ","Ẏ":"ẏ","Ẑ":"ẑ","Ẓ":"ẓ","Ẕ":"ẕ","ẖ":"ẖ","ẗ":"ẗ","ẘ":"ẘ","ẙ":"ẙ","ẚ":"aʾ","ẛ":"ṡ","ẞ":"ss","Ạ":"ạ","Ả":"ả","Ấ":"ấ","Ầ":"ầ","Ẩ":"ẩ","Ẫ":"ẫ","Ậ":"ậ","Ắ":"ắ","Ằ":"ằ","Ẳ":"ẳ","Ẵ":"ẵ","Ặ":"ặ","Ẹ":"ẹ","Ẻ":"ẻ","Ẽ":"ẽ","Ế":"ế","Ề":"ề","Ể":"ể","Ễ":"ễ","Ệ":"ệ","Ỉ":"ỉ","Ị":"ị","Ọ":"ọ","Ỏ":"ỏ","Ố":"ố","Ồ":"ồ","Ổ":"ổ","Ỗ":"ỗ","Ộ":"ộ","Ớ":"ớ","Ờ":"ờ","Ở":"ở","Ỡ":"ỡ","Ợ":"ợ","Ụ":"ụ","Ủ":"ủ","Ứ":"ứ","Ừ":"ừ","Ử":"ử","Ữ":"ữ","Ự":"ự","Ỳ":"ỳ","Ỵ":"ỵ","Ỷ":"ỷ","Ỹ":"ỹ","Ỻ":"ỻ","Ỽ":"ỽ","Ỿ":"ỿ","Ἀ":"ἀ","Ἁ":"ἁ","Ἂ":"ἂ","Ἃ":"ἃ","Ἄ":"ἄ","Ἅ":"ἅ","Ἆ":"ἆ","Ἇ":"ἇ","Ἐ":"ἐ","Ἑ":"ἑ","Ἒ":"ἒ","Ἓ":"ἓ","Ἔ":"ἔ","Ἕ":"ἕ","Ἠ":"ἠ","Ἡ":"ἡ","Ἢ":"ἢ","Ἣ":"ἣ","Ἤ":"ἤ","Ἥ":"ἥ","Ἦ":"ἦ","Ἧ":"ἧ","Ἰ":"ἰ","Ἱ":"ἱ","Ἲ":"ἲ","Ἳ":"ἳ","Ἴ":"ἴ","Ἵ":"ἵ","Ἶ":"ἶ","Ἷ":"ἷ","Ὀ":"ὀ","Ὁ":"ὁ","Ὂ":"ὂ","Ὃ":"ὃ","Ὄ":"ὄ","Ὅ":"ὅ","ὐ":"ὐ","ὒ":"ὒ","ὔ":"ὔ","ὖ":"ὖ","Ὑ":"ὑ","Ὓ":"ὓ","Ὕ":"ὕ","Ὗ":"ὗ","Ὠ":"ὠ","Ὡ":"ὡ","Ὢ":"ὢ","Ὣ":"ὣ","Ὤ":"ὤ","Ὥ":"ὥ","Ὦ":"ὦ","Ὧ":"ὧ","ᾀ":"ἀι","ᾁ":"ἁι","ᾂ":"ἂι","ᾃ":"ἃι","ᾄ":"ἄι","ᾅ":"ἅι","ᾆ":"ἆι","ᾇ":"ἇι","ᾈ":"ἀι","ᾉ":"ἁι","ᾊ":"ἂι","ᾋ":"ἃι","ᾌ":"ἄι","ᾍ":"ἅι","ᾎ":"ἆι","ᾏ":"ἇι","ᾐ":"ἠι","ᾑ":"ἡι","ᾒ":"ἢι","ᾓ":"ἣι","ᾔ":"ἤι","ᾕ":"ἥι","ᾖ":"ἦι","ᾗ":"ἧι","ᾘ":"ἠι","ᾙ":"ἡι","ᾚ":"ἢι","ᾛ":"ἣι","ᾜ":"ἤι","ᾝ":"ἥι","ᾞ":"ἦι","ᾟ":"ἧι","ᾠ":"ὠι","ᾡ":"ὡι","ᾢ":"ὢι","ᾣ":"ὣι","ᾤ":"ὤι","ᾥ":"ὥι","ᾦ":"ὦι","ᾧ":"ὧι","ᾨ":"ὠι","ᾩ":"ὡι","ᾪ":"ὢι","ᾫ":"ὣι","ᾬ":"ὤι","ᾭ":"ὥι","ᾮ":"ὦι","ᾯ":"ὧι","ᾲ":"ὰι","ᾳ":"αι","ᾴ":"άι","ᾶ":"ᾶ","ᾷ":"ᾶι","Ᾰ":"ᾰ","Ᾱ":"ᾱ","Ὰ":"ὰ","Ά":"ά","ᾼ":"αι","ι":"ι","ῂ":"ὴι","ῃ":"ηι","ῄ":"ήι","ῆ":"ῆ","ῇ":"ῆι","Ὲ":"ὲ","Έ":"έ","Ὴ":"ὴ","Ή":"ή","ῌ":"ηι","ῒ":"ῒ","ΐ":"ΐ","ῖ":"ῖ","ῗ":"ῗ","Ῐ":"ῐ","Ῑ":"ῑ","Ὶ":"ὶ","Ί":"ί","ῢ":"ῢ","ΰ":"ΰ","ῤ":"ῤ","ῦ":"ῦ","ῧ":"ῧ","Ῠ":"ῠ","Ῡ":"ῡ","Ὺ":"ὺ","Ύ":"ύ","Ῥ":"ῥ","ῲ":"ὼι","ῳ":"ωι","ῴ":"ώι","ῶ":"ῶ","ῷ":"ῶι","Ὸ":"ὸ","Ό":"ό","Ὼ":"ὼ","Ώ":"ώ","ῼ":"ωι","Ω":"ω","K":"k","Å":"å","Ⅎ":"ⅎ","Ⅰ":"ⅰ","Ⅱ":"ⅱ","Ⅲ":"ⅲ","Ⅳ":"ⅳ","Ⅴ":"ⅴ","Ⅵ":"ⅵ","Ⅶ":"ⅶ","Ⅷ":"ⅷ","Ⅸ":"ⅸ","Ⅹ":"ⅹ","Ⅺ":"ⅺ","Ⅻ":"ⅻ","Ⅼ":"ⅼ","Ⅽ":"ⅽ","Ⅾ":"ⅾ","Ⅿ":"ⅿ","Ↄ":"ↄ","Ⓐ":"ⓐ","Ⓑ":"ⓑ","Ⓒ":"ⓒ","Ⓓ":"ⓓ","Ⓔ":"ⓔ","Ⓕ":"ⓕ","Ⓖ":"ⓖ","Ⓗ":"ⓗ","Ⓘ":"ⓘ","Ⓙ":"ⓙ","Ⓚ":"ⓚ","Ⓛ":"ⓛ","Ⓜ":"ⓜ","Ⓝ":"ⓝ","Ⓞ":"ⓞ","Ⓟ":"ⓟ","Ⓠ":"ⓠ","Ⓡ":"ⓡ","Ⓢ":"ⓢ","Ⓣ":"ⓣ","Ⓤ":"ⓤ","Ⓥ":"ⓥ","Ⓦ":"ⓦ","Ⓧ":"ⓧ","Ⓨ":"ⓨ","Ⓩ":"ⓩ","Ⰰ":"ⰰ","Ⰱ":"ⰱ","Ⰲ":"ⰲ","Ⰳ":"ⰳ","Ⰴ":"ⰴ","Ⰵ":"ⰵ","Ⰶ":"ⰶ","Ⰷ":"ⰷ","Ⰸ":"ⰸ","Ⰹ":"ⰹ","Ⰺ":"ⰺ","Ⰻ":"ⰻ","Ⰼ":"ⰼ","Ⰽ":"ⰽ","Ⰾ":"ⰾ","Ⰿ":"ⰿ","Ⱀ":"ⱀ","Ⱁ":"ⱁ","Ⱂ":"ⱂ","Ⱃ":"ⱃ","Ⱄ":"ⱄ","Ⱅ":"ⱅ","Ⱆ":"ⱆ","Ⱇ":"ⱇ","Ⱈ":"ⱈ","Ⱉ":"ⱉ","Ⱊ":"ⱊ","Ⱋ":"ⱋ","Ⱌ":"ⱌ","Ⱍ":"ⱍ","Ⱎ":"ⱎ","Ⱏ":"ⱏ","Ⱐ":"ⱐ","Ⱑ":"ⱑ","Ⱒ":"ⱒ","Ⱓ":"ⱓ","Ⱔ":"ⱔ","Ⱕ":"ⱕ","Ⱖ":"ⱖ","Ⱗ":"ⱗ","Ⱘ":"ⱘ","Ⱙ":"ⱙ","Ⱚ":"ⱚ","Ⱛ":"ⱛ","Ⱜ":"ⱜ","Ⱝ":"ⱝ","Ⱞ":"ⱞ","Ⱟ":"ⱟ","Ⱡ":"ⱡ","Ɫ":"ɫ","Ᵽ":"ᵽ","Ɽ":"ɽ","Ⱨ":"ⱨ","Ⱪ":"ⱪ","Ⱬ":"ⱬ","Ɑ":"ɑ","Ɱ":"ɱ","Ɐ":"ɐ","Ɒ":"ɒ","Ⱳ":"ⱳ","Ⱶ":"ⱶ","Ȿ":"ȿ","Ɀ":"ɀ","Ⲁ":"ⲁ","Ⲃ":"ⲃ","Ⲅ":"ⲅ","Ⲇ":"ⲇ","Ⲉ":"ⲉ","Ⲋ":"ⲋ","Ⲍ":"ⲍ","Ⲏ":"ⲏ","Ⲑ":"ⲑ","Ⲓ":"ⲓ","Ⲕ":"ⲕ","Ⲗ":"ⲗ","Ⲙ":"ⲙ","Ⲛ":"ⲛ","Ⲝ":"ⲝ","Ⲟ":"ⲟ","Ⲡ":"ⲡ","Ⲣ":"ⲣ","Ⲥ":"ⲥ","Ⲧ":"ⲧ","Ⲩ":"ⲩ","Ⲫ":"ⲫ","Ⲭ":"ⲭ","Ⲯ":"ⲯ","Ⲱ":"ⲱ","Ⲳ":"ⲳ","Ⲵ":"ⲵ","Ⲷ":"ⲷ","Ⲹ":"ⲹ","Ⲻ":"ⲻ","Ⲽ":"ⲽ","Ⲿ":"ⲿ","Ⳁ":"ⳁ","Ⳃ":"ⳃ","Ⳅ":"ⳅ","Ⳇ":"ⳇ","Ⳉ":"ⳉ","Ⳋ":"ⳋ","Ⳍ":"ⳍ","Ⳏ":"ⳏ","Ⳑ":"ⳑ","Ⳓ":"ⳓ","Ⳕ":"ⳕ","Ⳗ":"ⳗ","Ⳙ":"ⳙ","Ⳛ":"ⳛ","Ⳝ":"ⳝ","Ⳟ":"ⳟ","Ⳡ":"ⳡ","Ⳣ":"ⳣ","Ⳬ":"ⳬ","Ⳮ":"ⳮ","Ⳳ":"ⳳ","Ꙁ":"ꙁ","Ꙃ":"ꙃ","Ꙅ":"ꙅ","Ꙇ":"ꙇ","Ꙉ":"ꙉ","Ꙋ":"ꙋ","Ꙍ":"ꙍ","Ꙏ":"ꙏ","Ꙑ":"ꙑ","Ꙓ":"ꙓ","Ꙕ":"ꙕ","Ꙗ":"ꙗ","Ꙙ":"ꙙ","Ꙛ":"ꙛ","Ꙝ":"ꙝ","Ꙟ":"ꙟ","Ꙡ":"ꙡ","Ꙣ":"ꙣ","Ꙥ":"ꙥ","Ꙧ":"ꙧ","Ꙩ":"ꙩ","Ꙫ":"ꙫ","Ꙭ":"ꙭ","Ꚁ":"ꚁ","Ꚃ":"ꚃ","Ꚅ":"ꚅ","Ꚇ":"ꚇ","Ꚉ":"ꚉ","Ꚋ":"ꚋ","Ꚍ":"ꚍ","Ꚏ":"ꚏ","Ꚑ":"ꚑ","Ꚓ":"ꚓ","Ꚕ":"ꚕ","Ꚗ":"ꚗ","Ꚙ":"ꚙ","Ꚛ":"ꚛ","Ꜣ":"ꜣ","Ꜥ":"ꜥ","Ꜧ":"ꜧ","Ꜩ":"ꜩ","Ꜫ":"ꜫ","Ꜭ":"ꜭ","Ꜯ":"ꜯ","Ꜳ":"ꜳ","Ꜵ":"ꜵ","Ꜷ":"ꜷ","Ꜹ":"ꜹ","Ꜻ":"ꜻ","Ꜽ":"ꜽ","Ꜿ":"ꜿ","Ꝁ":"ꝁ","Ꝃ":"ꝃ","Ꝅ":"ꝅ","Ꝇ":"ꝇ","Ꝉ":"ꝉ","Ꝋ":"ꝋ","Ꝍ":"ꝍ","Ꝏ":"ꝏ","Ꝑ":"ꝑ","Ꝓ":"ꝓ","Ꝕ":"ꝕ","Ꝗ":"ꝗ","Ꝙ":"ꝙ","Ꝛ":"ꝛ","Ꝝ":"ꝝ","Ꝟ":"ꝟ","Ꝡ":"ꝡ","Ꝣ":"ꝣ","Ꝥ":"ꝥ","Ꝧ":"ꝧ","Ꝩ":"ꝩ","Ꝫ":"ꝫ","Ꝭ":"ꝭ","Ꝯ":"ꝯ","Ꝺ":"ꝺ","Ꝼ":"ꝼ","Ᵹ":"ᵹ","Ꝿ":"ꝿ","Ꞁ":"ꞁ","Ꞃ":"ꞃ","Ꞅ":"ꞅ","Ꞇ":"ꞇ","Ꞌ":"ꞌ","Ɥ":"ɥ","Ꞑ":"ꞑ","Ꞓ":"ꞓ","Ꞗ":"ꞗ","Ꞙ":"ꞙ","Ꞛ":"ꞛ","Ꞝ":"ꞝ","Ꞟ":"ꞟ","Ꞡ":"ꞡ","Ꞣ":"ꞣ","Ꞥ":"ꞥ","Ꞧ":"ꞧ","Ꞩ":"ꞩ","Ɦ":"ɦ","Ɜ":"ɜ","Ɡ":"ɡ","Ɬ":"ɬ","Ɪ":"ɪ","Ʞ":"ʞ","Ʇ":"ʇ","Ʝ":"ʝ","Ꭓ":"ꭓ","Ꞵ":"ꞵ","Ꞷ":"ꞷ","Ꞹ":"ꞹ","Ꞻ":"ꞻ","Ꞽ":"ꞽ","Ꞿ":"ꞿ","Ꟁ":"ꟁ","Ꟃ":"ꟃ","Ꞔ":"ꞔ","Ʂ":"ʂ","Ᶎ":"ᶎ","Ꟈ":"ꟈ","Ꟊ":"ꟊ","Ɤ":"ɤ","Ꟍ":"ꟍ","꟎":"꟏","Ꟑ":"ꟑ","꟒":"ꟓ","꟔":"ꟕ","Ꟗ":"ꟗ","Ꟙ":"ꟙ","Ꟛ":"ꟛ","Ƛ":"ƛ","Ꟶ":"ꟶ","ꭰ":"Ꭰ","ꭱ":"Ꭱ","ꭲ":"Ꭲ","ꭳ":"Ꭳ","ꭴ":"Ꭴ","ꭵ":"Ꭵ","ꭶ":"Ꭶ","ꭷ":"Ꭷ","ꭸ":"Ꭸ","ꭹ":"Ꭹ","ꭺ":"Ꭺ","ꭻ":"Ꭻ","ꭼ":"Ꭼ","ꭽ":"Ꭽ","ꭾ":"Ꭾ","ꭿ":"Ꭿ","ꮀ":"Ꮀ","ꮁ":"Ꮁ","ꮂ":"Ꮂ","ꮃ":"Ꮃ","ꮄ":"Ꮄ","ꮅ":"Ꮅ","ꮆ":"Ꮆ","ꮇ":"Ꮇ","ꮈ":"Ꮈ","ꮉ":"Ꮉ","ꮊ":"Ꮊ","ꮋ":"Ꮋ","ꮌ":"Ꮌ","ꮍ":"Ꮍ","ꮎ":"Ꮎ","ꮏ":"Ꮏ","ꮐ":"Ꮐ","ꮑ":"Ꮑ","ꮒ":"Ꮒ","ꮓ":"Ꮓ","ꮔ":"Ꮔ","ꮕ":"Ꮕ","ꮖ":"Ꮖ","ꮗ":"Ꮗ","ꮘ":"Ꮘ","ꮙ":"Ꮙ","ꮚ":"Ꮚ","ꮛ":"Ꮛ","ꮜ":"Ꮜ","ꮝ":"Ꮝ","ꮞ":"Ꮞ","ꮟ":"Ꮟ","ꮠ":"Ꮠ","ꮡ":"Ꮡ","ꮢ":"Ꮢ","ꮣ":"Ꮣ","ꮤ":"Ꮤ","ꮥ":"Ꮥ","ꮦ":"Ꮦ","ꮧ":"Ꮧ","ꮨ":"Ꮨ","ꮩ":"Ꮩ","ꮪ":"Ꮪ","ꮫ":"Ꮫ","ꮬ":"Ꮬ","ꮭ":"Ꮭ","ꮮ":"Ꮮ","ꮯ":"Ꮯ","ꮰ":"Ꮰ","ꮱ":"Ꮱ","ꮲ":"Ꮲ","ꮳ":"Ꮳ","ꮴ":"Ꮴ","ꮵ":"Ꮵ","ꮶ":"Ꮶ","ꮷ":"Ꮷ","ꮸ":"Ꮸ","ꮹ":"Ꮹ","ꮺ":"Ꮺ","ꮻ":"Ꮻ","ꮼ":"Ꮼ","ꮽ":"Ꮽ","ꮾ":"Ꮾ","ꮿ":"Ꮿ","ff":"ff","fi":"fi","fl":"fl","ffi":"ffi","ffl":"ffl","ſt":"st","st":"st","ﬓ":"մն","ﬔ":"մե","ﬕ":"մի","ﬖ":"վն","ﬗ":"մխ","A":"a","B":"b","C":"c","D":"d","E":"e","F":"f","G":"g","H":"h","I":"i","J":"j","K":"k","L":"l","M":"m","N":"n","O":"o","P":"p","Q":"q","R":"r","S":"s","T":"t","U":"u","V":"v","W":"w","X":"x","Y":"y","Z":"z","𐐀":"𐐨","𐐁":"𐐩","𐐂":"𐐪","𐐃":"𐐫","𐐄":"𐐬","𐐅":"𐐭","𐐆":"𐐮","𐐇":"𐐯","𐐈":"𐐰","𐐉":"𐐱","𐐊":"𐐲","𐐋":"𐐳","𐐌":"𐐴","𐐍":"𐐵","𐐎":"𐐶","𐐏":"𐐷","𐐐":"𐐸","𐐑":"𐐹","𐐒":"𐐺","𐐓":"𐐻","𐐔":"𐐼","𐐕":"𐐽","𐐖":"𐐾","𐐗":"𐐿","𐐘":"𐑀","𐐙":"𐑁","𐐚":"𐑂","𐐛":"𐑃","𐐜":"𐑄","𐐝":"𐑅","𐐞":"𐑆","𐐟":"𐑇","𐐠":"𐑈","𐐡":"𐑉","𐐢":"𐑊","𐐣":"𐑋","𐐤":"𐑌","𐐥":"𐑍","𐐦":"𐑎","𐐧":"𐑏","𐒰":"𐓘","𐒱":"𐓙","𐒲":"𐓚","𐒳":"𐓛","𐒴":"𐓜","𐒵":"𐓝","𐒶":"𐓞","𐒷":"𐓟","𐒸":"𐓠","𐒹":"𐓡","𐒺":"𐓢","𐒻":"𐓣","𐒼":"𐓤","𐒽":"𐓥","𐒾":"𐓦","𐒿":"𐓧","𐓀":"𐓨","𐓁":"𐓩","𐓂":"𐓪","𐓃":"𐓫","𐓄":"𐓬","𐓅":"𐓭","𐓆":"𐓮","𐓇":"𐓯","𐓈":"𐓰","𐓉":"𐓱","𐓊":"𐓲","𐓋":"𐓳","𐓌":"𐓴","𐓍":"𐓵","𐓎":"𐓶","𐓏":"𐓷","𐓐":"𐓸","𐓑":"𐓹","𐓒":"𐓺","𐓓":"𐓻","𐕰":"𐖗","𐕱":"𐖘","𐕲":"𐖙","𐕳":"𐖚","𐕴":"𐖛","𐕵":"𐖜","𐕶":"𐖝","𐕷":"𐖞","𐕸":"𐖟","𐕹":"𐖠","𐕺":"𐖡","𐕼":"𐖣","𐕽":"𐖤","𐕾":"𐖥","𐕿":"𐖦","𐖀":"𐖧","𐖁":"𐖨","𐖂":"𐖩","𐖃":"𐖪","𐖄":"𐖫","𐖅":"𐖬","𐖆":"𐖭","𐖇":"𐖮","𐖈":"𐖯","𐖉":"𐖰","𐖊":"𐖱","𐖌":"𐖳","𐖍":"𐖴","𐖎":"𐖵","𐖏":"𐖶","𐖐":"𐖷","𐖑":"𐖸","𐖒":"𐖹","𐖔":"𐖻","𐖕":"𐖼","𐲀":"𐳀","𐲁":"𐳁","𐲂":"𐳂","𐲃":"𐳃","𐲄":"𐳄","𐲅":"𐳅","𐲆":"𐳆","𐲇":"𐳇","𐲈":"𐳈","𐲉":"𐳉","𐲊":"𐳊","𐲋":"𐳋","𐲌":"𐳌","𐲍":"𐳍","𐲎":"𐳎","𐲏":"𐳏","𐲐":"𐳐","𐲑":"𐳑","𐲒":"𐳒","𐲓":"𐳓","𐲔":"𐳔","𐲕":"𐳕","𐲖":"𐳖","𐲗":"𐳗","𐲘":"𐳘","𐲙":"𐳙","𐲚":"𐳚","𐲛":"𐳛","𐲜":"𐳜","𐲝":"𐳝","𐲞":"𐳞","𐲟":"𐳟","𐲠":"𐳠","𐲡":"𐳡","𐲢":"𐳢","𐲣":"𐳣","𐲤":"𐳤","𐲥":"𐳥","𐲦":"𐳦","𐲧":"𐳧","𐲨":"𐳨","𐲩":"𐳩","𐲪":"𐳪","𐲫":"𐳫","𐲬":"𐳬","𐲭":"𐳭","𐲮":"𐳮","𐲯":"𐳯","𐲰":"𐳰","𐲱":"𐳱","𐲲":"𐳲","𐵐":"𐵰","𐵑":"𐵱","𐵒":"𐵲","𐵓":"𐵳","𐵔":"𐵴","𐵕":"𐵵","𐵖":"𐵶","𐵗":"𐵷","𐵘":"𐵸","𐵙":"𐵹","𐵚":"𐵺","𐵛":"𐵻","𐵜":"𐵼","𐵝":"𐵽","𐵞":"𐵾","𐵟":"𐵿","𐵠":"𐶀","𐵡":"𐶁","𐵢":"𐶂","𐵣":"𐶃","𐵤":"𐶄","𐵥":"𐶅","𑢠":"𑣀","𑢡":"𑣁","𑢢":"𑣂","𑢣":"𑣃","𑢤":"𑣄","𑢥":"𑣅","𑢦":"𑣆","𑢧":"𑣇","𑢨":"𑣈","𑢩":"𑣉","𑢪":"𑣊","𑢫":"𑣋","𑢬":"𑣌","𑢭":"𑣍","𑢮":"𑣎","𑢯":"𑣏","𑢰":"𑣐","𑢱":"𑣑","𑢲":"𑣒","𑢳":"𑣓","𑢴":"𑣔","𑢵":"𑣕","𑢶":"𑣖","𑢷":"𑣗","𑢸":"𑣘","𑢹":"𑣙","𑢺":"𑣚","𑢻":"𑣛","𑢼":"𑣜","𑢽":"𑣝","𑢾":"𑣞","𑢿":"𑣟","𖹀":"𖹠","𖹁":"𖹡","𖹂":"𖹢","𖹃":"𖹣","𖹄":"𖹤","𖹅":"𖹥","𖹆":"𖹦","𖹇":"𖹧","𖹈":"𖹨","𖹉":"𖹩","𖹊":"𖹪","𖹋":"𖹫","𖹌":"𖹬","𖹍":"𖹭","𖹎":"𖹮","𖹏":"𖹯","𖹐":"𖹰","𖹑":"𖹱","𖹒":"𖹲","𖹓":"𖹳","𖹔":"𖹴","𖹕":"𖹵","𖹖":"𖹶","𖹗":"𖹷","𖹘":"𖹸","𖹙":"𖹹","𖹚":"𖹺","𖹛":"𖹻","𖹜":"𖹼","𖹝":"𖹽","𖹞":"𖹾","𖹟":"𖹿","𖺠":"𖺻","𖺡":"𖺼","𖺢":"𖺽","𖺣":"𖺾","𖺤":"𖺿","𖺥":"𖻀","𖺦":"𖻁","𖺧":"𖻂","𖺨":"𖻃","𖺩":"𖻄","𖺪":"𖻅","𖺫":"𖻆","𖺬":"𖻇","𖺭":"𖻈","𖺮":"𖻉","𖺯":"𖻊","𖺰":"𖻋","𖺱":"𖻌","𖺲":"𖻍","𖺳":"𖻎","𖺴":"𖻏","𖺵":"𖻐","𖺶":"𖻑","𖺷":"𖻒","𖺸":"𖻓","𞤀":"𞤢","𞤁":"𞤣","𞤂":"𞤤","𞤃":"𞤥","𞤄":"𞤦","𞤅":"𞤧","𞤆":"𞤨","𞤇":"𞤩","𞤈":"𞤪","𞤉":"𞤫","𞤊":"𞤬","𞤋":"𞤭","𞤌":"𞤮","𞤍":"𞤯","𞤎":"𞤰","𞤏":"𞤱","𞤐":"𞤲","𞤑":"𞤳","𞤒":"𞤴","𞤓":"𞤵","𞤔":"𞤶","𞤕":"𞤷","𞤖":"𞤸","𞤗":"𞤹","𞤘":"𞤺","𞤙":"𞤻","𞤚":"𞤼","𞤛":"𞤽","𞤜":"𞤾","𞤝":"𞤿","𞤞":"𞥀","𞤟":"𞥁","𞤠":"𞥂","𞤡":"𞥃"},"configuration":{"browser.enabled":{"doctor_check":null,"setting":"browser.enabled"},"browser.session_ttl":{"doctor_check":null,"setting":"browser.session_ttl"},"hosting.base_capacity_bytes":{"doctor_check":"hosting","setting":"hosting_base_capacity_bytes"},"hosting.content_group_read":{"doctor_check":null,"setting":"hosting_content_group_read"},"hosting.recovery_marker":{"doctor_check":null,"setting":"hosting_recovery_marker"},"identity.credential_delivery_recovery_window":{"doctor_check":"credential_delivery","setting":"credential_delivery_recovery_window"},"identity.handle_rename_enabled":{"doctor_check":"authority_snapshot","setting":"handle_rename_enabled"},"limits.part_bytes":{"doctor_check":null,"setting":"max_part_bytes"},"limits.path_bytes":{"doctor_check":null,"setting":"server.limits.max_path_bytes"},"limits.request_bytes":{"doctor_check":null,"setting":"server.limits.max_request_bytes"},"limits.response_bytes":{"doctor_check":null,"setting":"server.limits.max_response_bytes"},"mail.credential_file":{"doctor_check":null,"setting":"server.mail.credential_file"},"mail.enabled":{"doctor_check":null,"setting":"server.mail.enabled"},"mail.host":{"doctor_check":null,"setting":"server.mail.host"},"mail.port":{"doctor_check":null,"setting":"server.mail.port"},"mail.sender":{"doctor_check":null,"setting":"server.mail.sender"},"mail.tls":{"doctor_check":null,"setting":"server.mail.tls"},"money.allow_overdraft":{"doctor_check":"money_config","setting":"money.allow_overdraft"},"money.code":{"doctor_check":"money_config","setting":"money.code"},"money.currency_id":{"doctor_check":"money_config","setting":"money.currency_id"},"money.display_name":{"doctor_check":"money_config","setting":"money.display_name"},"money.enabled":{"doctor_check":"money_config","setting":"money.enabled"},"money.scale":{"doctor_check":"money_config","setting":"money.scale"},"money.transfer_fee":{"doctor_check":"money_config","setting":"money.transfer_fee"},"plugins.enabled":{"doctor_check":null,"setting":"server.plugins"},"recovery.custodians":{"doctor_check":null,"setting":"recovery_custodians"},"recovery.custodians.description":{"doctor_check":null,"setting":"recovery_custodians.0.description"},"recovery.custodians.id":{"doctor_check":null,"setting":"recovery_custodians.0.id"},"recovery.custodians.name":{"doctor_check":null,"setting":"recovery_custodians.0.name"},"recovery.custodians.policy_ref":{"doctor_check":null,"setting":"recovery_custodians.0.policy_ref"},"recovery.custodians.recipient":{"doctor_check":null,"setting":"recovery_custodians.0.recipient"},"server.listen":{"doctor_check":null,"setting":"listen"},"server.port":{"doctor_check":null,"setting":"port"},"server.public_web_origin":{"doctor_check":"hosting","setting":"public_web_origin"},"server.service_aliases":{"doctor_check":null,"setting":"service_aliases"},"server.service_url":{"doctor_check":null,"setting":"service_url"},"server.temporary_ttl":{"doctor_check":null,"setting":"temporary_ttl"},"server.transfer_ttl":{"doctor_check":null,"setting":"transfer_ttl"},"storage.blobs":{"doctor_check":null,"setting":"server.blob_dir"},"storage.content":{"doctor_check":"content_layout","setting":"server.content_dir"},"storage.postgres_dsn":{"doctor_check":"storage","setting":"server.postgres_dsn"},"storage.repositories":{"doctor_check":null,"setting":"server.repositories_dir"},"storage.service_keys":{"doctor_check":"online_ca","setting":"server.service_keys_dir"},"storage.staging":{"doctor_check":"capacity","setting":"server.staging_dir"},"storage.valkey_url":{"doctor_check":"valkey","setting":"server.valkey_url"},"tools.isolation":{"doctor_check":null,"setting":"worker_isolation"},"tools.max_response_bytes":{"doctor_check":null,"setting":"tool_max_response_bytes"},"tools.methods":{"doctor_check":null,"setting":"tool_methods"},"tools.ports":{"doctor_check":null,"setting":"tool_ports"},"tools.timeout_ms":{"doctor_check":null,"setting":"tool_timeout_ms"},"websub.hubs":{"doctor_check":null,"setting":"websub_hubs"}},"operations":[{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.achievements:install..answer","input_schema":{"id":"schema:achievement.answer:1","revision":null},"name":"achievement.answer","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.achievements:install..complete","input_schema":{"id":"schema:achievement.finish:1","revision":null},"name":"achievement.finish","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.achievements:install..list_grants","input_schema":{"id":"schema:achievement.list:1","revision":null},"name":"achievement.list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.achievements:install..pin","input_schema":{"id":"schema:achievement.pin:1","revision":null},"name":"achievement.pin","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.achievements:install..reorder","input_schema":{"id":"schema:achievement.reorder:1","revision":null},"name":"achievement.reorder","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.achievements:install..start","input_schema":{"id":"schema:achievement.start:1","revision":null},"name":"achievement.start","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.achievements:install..unpin","input_schema":{"id":"schema:achievement.unpin:1","revision":null},"name":"achievement.unpin","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.batch:install..atomic","input_schema":{"id":"schema:batch.atomic:1","revision":null},"name":"batch.atomic","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"external","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.batch:install..independent","input_schema":{"id":"schema:batch.independent:1","revision":null},"name":"batch.independent","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bounty:install..challenge","input_schema":{"id":"schema:bounty.challenge:1","revision":null},"name":"bounty.challenge","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bounty:install..claim","input_schema":{"id":"schema:bounty.claim:1","revision":null},"name":"bounty.claim","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bounty:install..claims","input_schema":{"id":"schema:bounty.claims:1","revision":null},"name":"bounty.claims","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bounty:install..close","input_schema":{"id":"schema:bounty.close:1","revision":null},"name":"bounty.close","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bounty:install..create","input_schema":{"id":"schema:bounty.create:1","revision":null},"name":"bounty.create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bounty:install..get","input_schema":{"id":"schema:bounty.get:1","revision":null},"name":"bounty.get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bounty:install..pause","input_schema":{"id":"schema:bounty.pause:1","revision":null},"name":"bounty.pause","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bounty:install..resume","input_schema":{"id":"schema:bounty.resume:1","revision":null},"name":"bounty.resume","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bounty:install..top_up","input_schema":{"id":"schema:bounty.top_up:1","revision":null},"name":"bounty.top_up","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..cert_cancel","input_schema":{"id":"schema:cert.cancel:1","revision":null},"name":"cert.cancel","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..cert_get","input_schema":{"id":"schema:cert.get:1","revision":null},"name":"cert.get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..cert_publish","input_schema":{"id":"schema:cert.publish:1","revision":null},"name":"cert.publish","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..cert_request","input_schema":{"id":"schema:cert.request:1","revision":null},"name":"cert.request","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..cert_revoke","input_schema":{"id":"schema:cert.revoke:1","revision":null},"name":"cert.revoke","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.agent_follows:install..listing","input_schema":{"id":"schema:communication.agent_following:1","revision":null},"name":"communication.agent_following","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..changes","input_schema":{"id":"schema:communication.changes:1","revision":null},"name":"communication.changes","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..changes","input_schema":{"id":"schema:communication.changes:2","revision":null},"name":"communication.changes","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..checkpoint_create","input_schema":{"id":"schema:communication.checkpoint_create:1","revision":null},"name":"communication.checkpoint_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_reads..get","input_schema":{"id":"schema:communication.checkpoint_get:1","revision":null},"name":"communication.checkpoint_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_reads..list_records","input_schema":{"id":"schema:communication.checkpoint_list:1","revision":null},"name":"communication.checkpoint_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..claim_create","input_schema":{"id":"schema:communication.claim_create:1","revision":null},"name":"communication.claim_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..claim_get","input_schema":{"id":"schema:communication.claim_get:1","revision":null},"name":"communication.claim_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..claim_list","input_schema":{"id":"schema:communication.claim_list:1","revision":null},"name":"communication.claim_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..conversation_get","input_schema":{"id":"schema:communication.conversation_get:1","revision":null},"name":"communication.conversation_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_decision","input_schema":{"id":"schema:communication.dm_accept:1","revision":null},"name":"communication.dm_accept","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_decision_token","input_schema":{"id":"schema:communication.dm_accept:2","revision":null},"name":"communication.dm_accept","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_archive","input_schema":{"id":"schema:communication.dm_archive:1","revision":null},"name":"communication.dm_archive","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_block","input_schema":{"id":"schema:communication.dm_block:1","revision":null},"name":"communication.dm_block","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_list","input_schema":{"id":"schema:communication.dm_list:1","revision":null},"name":"communication.dm_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_decision","input_schema":{"id":"schema:communication.dm_reject:1","revision":null},"name":"communication.dm_reject","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_decision_token","input_schema":{"id":"schema:communication.dm_reject:2","revision":null},"name":"communication.dm_reject","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_request","input_schema":{"id":"schema:communication.dm_request:1","revision":null},"name":"communication.dm_request","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_request","input_schema":{"id":"schema:communication.dm_request:2","revision":null},"name":"communication.dm_request","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_request_token","input_schema":{"id":"schema:communication.dm_request:3","revision":null},"name":"communication.dm_request","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":3},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_send","input_schema":{"id":"schema:communication.dm_send:1","revision":null},"name":"communication.dm_send","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..dm_send_token","input_schema":{"id":"schema:communication.dm_send:2","revision":null},"name":"communication.dm_send","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.agent_drops:install..cancel","input_schema":{"id":"schema:communication.drop_cancel:1","revision":null},"name":"communication.drop_cancel","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.agent_drops:install..claim","input_schema":{"id":"schema:communication.drop_claim:1","revision":null},"name":"communication.drop_claim","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.agent_drops:install..deposit","input_schema":{"id":"schema:communication.drop_deposit:1","revision":null},"name":"communication.drop_deposit","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.agent_drops:install..listing","input_schema":{"id":"schema:communication.drop_list:1","revision":null},"name":"communication.drop_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.event_stream:install..event_get","input_schema":{"id":"schema:communication.event:1","revision":null},"name":"communication.event","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.event_stream:install..events","input_schema":{"id":"schema:communication.events:1","revision":null},"name":"communication.events","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.event_stream:install..events","input_schema":{"id":"schema:communication.events:2","revision":null},"name":"communication.events","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.agent_follows:install..change","input_schema":{"id":"schema:communication.follow:1","revision":null},"name":"communication.follow","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.agent_follows:install..listing","input_schema":{"id":"schema:communication.followers:1","revision":null},"name":"communication.followers","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.following:install..following","input_schema":{"id":"schema:communication.following:1","revision":null},"name":"communication.following","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.game:install..join_ticket","input_schema":{"id":"schema:communication.game_join_ticket:1","revision":null},"name":"communication.game_join_ticket","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.game:install..status","input_schema":{"id":"schema:communication.game_webhook_status:1","revision":null},"name":"communication.game_webhook_status","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.game:install..subscribe","input_schema":{"id":"schema:communication.game_webhook_subscribe:1","revision":null},"name":"communication.game_webhook_subscribe","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.game:install..unsubscribe","input_schema":{"id":"schema:communication.game_webhook_unsubscribe:1","revision":null},"name":"communication.game_webhook_unsubscribe","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..handoff_create","input_schema":{"id":"schema:communication.handoff_create:1","revision":null},"name":"communication.handoff_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..handoff_create","input_schema":{"id":"schema:communication.handoff_create:2","revision":null},"name":"communication.handoff_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..handoff_decide","input_schema":{"id":"schema:communication.handoff_decide:1","revision":null},"name":"communication.handoff_decide","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..handoff_get","input_schema":{"id":"schema:communication.handoff_get:1","revision":null},"name":"communication.handoff_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..handoff_list","input_schema":{"id":"schema:communication.handoff_list:1","revision":null},"name":"communication.handoff_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..mailbox","input_schema":{"id":"schema:communication.inbox:1","revision":null},"name":"communication.inbox","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.internet:install..allow","input_schema":{"id":"schema:communication.internet_allow:1","revision":null},"name":"communication.internet_allow","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.internet:install..delete","input_schema":{"id":"schema:communication.internet_delete:1","revision":null},"name":"communication.internet_delete","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.internet:install..inbox","input_schema":{"id":"schema:communication.internet_inbox:1","revision":null},"name":"communication.internet_inbox","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":true,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.internet:install..receive","input_schema":{"id":"schema:communication.internet_receive:1","revision":null},"name":"communication.internet_receive","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.internet:install..revoke","input_schema":{"id":"schema:communication.internet_revoke:1","revision":null},"name":"communication.internet_revoke","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..lease_acquire","input_schema":{"id":"schema:communication.lease_acquire:1","revision":null},"name":"communication.lease_acquire","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..lease_get","input_schema":{"id":"schema:communication.lease_get:1","revision":null},"name":"communication.lease_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..lease_list","input_schema":{"id":"schema:communication.lease_list:1","revision":null},"name":"communication.lease_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..lease_release","input_schema":{"id":"schema:communication.lease_release:1","revision":null},"name":"communication.lease_release","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration:install..lease_renew","input_schema":{"id":"schema:communication.lease_renew:1","revision":null},"name":"communication.lease_renew","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..offer_create","input_schema":{"id":"schema:communication.offer_create:1","revision":null},"name":"communication.offer_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_reads..get","input_schema":{"id":"schema:communication.offer_get:1","revision":null},"name":"communication.offer_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_reads..list_records","input_schema":{"id":"schema:communication.offer_list:1","revision":null},"name":"communication.offer_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_transition..transition","input_schema":{"id":"schema:communication.offer_withdraw:1","revision":null},"name":"communication.offer_withdraw","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..mailbox","input_schema":{"id":"schema:communication.outbox:1","revision":null},"name":"communication.outbox","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..presence_clear","input_schema":{"id":"schema:communication.presence_clear:1","revision":null},"name":"communication.presence_clear","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..presence_get","input_schema":{"id":"schema:communication.presence_get:1","revision":null},"name":"communication.presence_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..presence_set","input_schema":{"id":"schema:communication.presence_set:1","revision":null},"name":"communication.presence_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.proposals:install..install_decision..decide","input_schema":{"id":"schema:communication.proposal_accept:1","revision":null},"name":"communication.proposal_accept","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.proposals:install..create","input_schema":{"id":"schema:communication.proposal_create:1","revision":null},"name":"communication.proposal_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_reads..get","input_schema":{"id":"schema:communication.proposal_get:1","revision":null},"name":"communication.proposal_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_reads..list_records","input_schema":{"id":"schema:communication.proposal_list:1","revision":null},"name":"communication.proposal_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.proposals:install..install_decision..decide","input_schema":{"id":"schema:communication.proposal_reject:1","revision":null},"name":"communication.proposal_reject","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.proposals:install..install_decision..decide","input_schema":{"id":"schema:communication.proposal_withdraw:1","revision":null},"name":"communication.proposal_withdraw","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.receipts:install..receipt_get","input_schema":{"id":"schema:communication.receipt_get:1","revision":null},"name":"communication.receipt_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.receipts:install..receipt_list","input_schema":{"id":"schema:communication.receipt_list:1","revision":null},"name":"communication.receipt_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_transition..transition","input_schema":{"id":"schema:communication.request_cancel:1","revision":null},"name":"communication.request_cancel","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_transition..transition","input_schema":{"id":"schema:communication.request_claim:1","revision":null},"name":"communication.request_claim","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..request_create","input_schema":{"id":"schema:communication.request_create:1","revision":null},"name":"communication.request_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_transition..transition","input_schema":{"id":"schema:communication.request_fulfill:1","revision":null},"name":"communication.request_fulfill","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_reads..get","input_schema":{"id":"schema:communication.request_get:1","revision":null},"name":"communication.request_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.collaboration_resources:install..install_reads..list_records","input_schema":{"id":"schema:communication.request_list:1","revision":null},"name":"communication.request_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..send","input_schema":{"id":"schema:communication.send:1","revision":null},"name":"communication.send","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..sync","input_schema":{"id":"schema:communication.sync:1","revision":null},"name":"communication.sync","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..sync_checkpoint_ack","input_schema":{"id":"schema:communication.sync_checkpoint_ack:1","revision":null},"name":"communication.sync_checkpoint_ack","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..sync_checkpoint_open","input_schema":{"id":"schema:communication.sync_checkpoint_open:1","revision":null},"name":"communication.sync_checkpoint_open","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.task_plans:install..claim","input_schema":{"id":"schema:communication.task_claim:1","revision":null},"name":"communication.task_claim","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.task_plans:install..complete","input_schema":{"id":"schema:communication.task_complete:1","revision":null},"name":"communication.task_complete","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.task_plans:install..create","input_schema":{"id":"schema:communication.task_plan_create:1","revision":null},"name":"communication.task_plan_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.task_plans:install..renew","input_schema":{"id":"schema:communication.task_renew:1","revision":null},"name":"communication.task_renew","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.task_plans:install..status","input_schema":{"id":"schema:communication.task_status:1","revision":null},"name":"communication.task_status","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.agent_follows:install..change","input_schema":{"id":"schema:communication.unfollow:1","revision":null},"name":"communication.unfollow","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..watch","input_schema":{"id":"schema:communication.unwatch:1","revision":null},"name":"communication.unwatch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..watch","input_schema":{"id":"schema:communication.watch:1","revision":null},"name":"communication.watch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.watches:install..watch_cancel","input_schema":{"id":"schema:communication.watch_cancel:1","revision":null},"name":"communication.watch_cancel","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.watches:install..watch_create","input_schema":{"id":"schema:communication.watch_create:1","revision":null},"name":"communication.watch_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.watches:install..watch_query_create","input_schema":{"id":"schema:communication.watch_create:2","revision":null},"name":"communication.watch_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.watches:install..watch_get","input_schema":{"id":"schema:communication.watch_get:1","revision":null},"name":"communication.watch_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.watches:install..watch_list","input_schema":{"id":"schema:communication.watch_list:1","revision":null},"name":"communication.watch_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..webhook_disable","input_schema":{"id":"schema:communication.webhook_disable:1","revision":null},"name":"communication.webhook_disable","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..webhook_set","input_schema":{"id":"schema:communication.webhook_set:1","revision":null},"name":"communication.webhook_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..webhook_status","input_schema":{"id":"schema:communication.webhook_status:1","revision":null},"name":"communication.webhook_status","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..webhook_subscribe","input_schema":{"id":"schema:communication.webhook_subscribe:1","revision":null},"name":"communication.webhook_subscribe","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..webhook_subscription","input_schema":{"id":"schema:communication.webhook_subscription:1","revision":null},"name":"communication.webhook_subscription","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.communication:install..webhook_unsubscribe","input_schema":{"id":"schema:communication.webhook_unsubscribe:1","revision":null},"name":"communication.webhook_unsubscribe","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..lifecycle","input_schema":{"id":"schema:content.archive:1","revision":null},"name":"content.archive","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..attach","input_schema":{"id":"schema:content.attach:1","revision":null},"name":"content.attach","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"chgrp","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..chgrp","input_schema":{"id":"schema:content.chgrp:1","revision":null},"name":"content.chgrp","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"chmod","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..chmod","input_schema":{"id":"schema:content.chmod:1","revision":null},"name":"content.chmod","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"chown","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..chown","input_schema":{"id":"schema:content.chown:1","revision":null},"name":"content.chown","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"parent","check":"create","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..file_put","input_schema":{"id":"schema:content.file_put:1","revision":null},"name":"content.file_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..move","input_schema":{"id":"schema:content.move:1","revision":null},"name":"content.move","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"parent","check":"create","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..post_create","input_schema":{"id":"schema:content.post_create:1","revision":null},"name":"content.post_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"parent","check":"create","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..post_create","input_schema":{"id":"schema:content.post_create:2","revision":null},"name":"content.post_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..post_edit","input_schema":{"id":"schema:content.post_edit:1","revision":null},"name":"content.post_edit","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..post_edit","input_schema":{"id":"schema:content.post_edit:2","revision":null},"name":"content.post_edit","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..post_edit_metadata","input_schema":{"id":"schema:content.post_edit_metadata:1","revision":null},"name":"content.post_edit_metadata","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..text_patch","input_schema":{"id":"schema:content.post_patch:1","revision":null},"name":"content.post_patch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..text_patch","input_schema":{"id":"schema:content.post_patch:2","revision":null},"name":"content.post_patch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..post_rollback","input_schema":{"id":"schema:content.post_rollback:1","revision":null},"name":"content.post_rollback","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..post_edit","input_schema":{"id":"schema:content.post_write:1","revision":null},"name":"content.post_write","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..post_edit","input_schema":{"id":"schema:content.post_write:2","revision":null},"name":"content.post_write","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.public_board:install..update","input_schema":{"id":"schema:content.public_board_update:1","revision":null},"name":"content.public_board_update","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"purge","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..purge","input_schema":{"id":"schema:content.purge:1","revision":null},"name":"content.purge","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..lifecycle","input_schema":{"id":"schema:content.restore:1","revision":null},"name":"content.restore","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..tags_set","input_schema":{"id":"schema:content.tags_set:1","revision":null},"name":"content.tags_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"parent","check":"create","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..template_put","input_schema":{"id":"schema:content.template_put:1","revision":null},"name":"content.template_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..text_patch","input_schema":{"id":"schema:content.text_patch:1","revision":null},"name":"content.text_patch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..text_patch","input_schema":{"id":"schema:content.text_patch:2","revision":null},"name":"content.text_patch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..text_patch","input_schema":{"id":"schema:content.text_patch:3","revision":null},"name":"content.text_patch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":3},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..text_patch_batch","input_schema":{"id":"schema:content.text_patch_batch:1","revision":null},"name":"content.text_patch_batch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..text_patch_batch","input_schema":{"id":"schema:content.text_patch_batch:2","revision":null},"name":"content.text_patch_batch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_approve","input_schema":{"id":"schema:content.topic_approve:1","revision":null},"name":"content.topic_approve","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_ban","input_schema":{"id":"schema:content.topic_ban:1","revision":null},"name":"content.topic_ban","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"manage","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..configure","input_schema":{"id":"schema:content.topic_configure:1","revision":null},"name":"content.topic_configure","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"access":{"argument":"id","check":"manage","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..configure","input_schema":{"id":"schema:content.topic_configure:2","revision":null},"name":"content.topic_configure","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"access":{"argument":"parent","check":"create","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_create","input_schema":{"id":"schema:content.topic_create:1","revision":null},"name":"content.topic_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..update_membership","input_schema":{"id":"schema:content.topic_demote:1","revision":null},"name":"content.topic_demote","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_events","input_schema":{"id":"schema:content.topic_events:1","revision":null},"name":"content.topic_events","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_invite","input_schema":{"id":"schema:content.topic_invite:1","revision":null},"name":"content.topic_invite","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_join","input_schema":{"id":"schema:content.topic_join:1","revision":null},"name":"content.topic_join","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_leave","input_schema":{"id":"schema:content.topic_leave:1","revision":null},"name":"content.topic_leave","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_members","input_schema":{"id":"schema:content.topic_members:1","revision":null},"name":"content.topic_members","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_policy_set","input_schema":{"id":"schema:content.topic_policy_set:1","revision":null},"name":"content.topic_policy_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..update_membership","input_schema":{"id":"schema:content.topic_promote:1","revision":null},"name":"content.topic_promote","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..update_membership","input_schema":{"id":"schema:content.topic_remove:1","revision":null},"name":"content.topic_remove","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_unban","input_schema":{"id":"schema:content.topic_unban:1","revision":null},"name":"content.topic_unban","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.delivery:install..accept","input_schema":{"id":"schema:delivery.accept:1","revision":null},"name":"delivery.accept","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.delivery:install..claim","input_schema":{"id":"schema:delivery.accept:2","revision":null},"name":"delivery.accept","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.delivery:install..claim","input_schema":{"id":"schema:delivery.claim:1","revision":null},"name":"delivery.claim","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.delivery:install..get","input_schema":{"id":"schema:delivery.get:1","revision":null},"name":"delivery.get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.market.delivery:install..get","input_schema":{"id":"schema:delivery.get:2","revision":null},"name":"delivery.get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.delivery:install..notify","input_schema":{"id":"schema:delivery.notify:1","revision":null},"name":"delivery.notify","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.market.delivery:install..part_get","input_schema":{"id":"schema:delivery.part_get:1","revision":null},"name":"delivery.part_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.delivery:install..prepare","input_schema":{"id":"schema:delivery.prepare:1","revision":null},"name":"delivery.prepare","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.delivery:install..prepare_managed","input_schema":{"id":"schema:delivery.prepare:2","revision":null},"name":"delivery.prepare","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.delivery:install..submit","input_schema":{"id":"schema:delivery.submit:1","revision":null},"name":"delivery.submit","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.delivery:install..transfer_open","input_schema":{"id":"schema:delivery.transfer_open:1","revision":null},"name":"delivery.transfer_open","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..capabilities","input_schema":{"id":"schema:discovery.capabilities:1","revision":null},"name":"discovery.capabilities","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..diff","input_schema":{"id":"schema:discovery.diff:1","revision":null},"name":"discovery.diff","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..diff_view","input_schema":{"id":"schema:discovery.diff_view:1","revision":null},"name":"discovery.diff_view","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.rss:register..feed","input_schema":{"id":"schema:discovery.feed:1","revision":null},"name":"discovery.feed","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..get","input_schema":{"id":"schema:discovery.get:1","revision":null},"name":"discovery.get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..grep","input_schema":{"id":"schema:discovery.grep:1","revision":null},"name":"discovery.grep","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..lexical_search","input_schema":{"id":"schema:discovery.lexical_search:1","revision":null},"name":"discovery.lexical_search","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..lexical_search","input_schema":{"id":"schema:discovery.lexical_search:2","revision":null},"name":"discovery.lexical_search","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..lexical_search","input_schema":{"id":"schema:discovery.lexical_search:3","revision":null},"name":"discovery.lexical_search","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":3},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..lexical_search","input_schema":{"id":"schema:discovery.lexical_search:4","revision":null},"name":"discovery.lexical_search","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":4},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..lexical_search","input_schema":{"id":"schema:discovery.lexical_search:5","revision":null},"name":"discovery.lexical_search","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":5},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..links","input_schema":{"id":"schema:discovery.links:1","revision":null},"name":"discovery.links","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..list_items","input_schema":{"id":"schema:discovery.list:1","revision":null},"name":"discovery.list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":true,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.live_space:install..now","input_schema":{"id":"schema:discovery.now:1","revision":null},"name":"discovery.now","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..operations","input_schema":{"id":"schema:discovery.operations:1","revision":null},"name":"discovery.operations","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.public_board:install..get","input_schema":{"id":"schema:discovery.public_board:1","revision":null},"name":"discovery.public_board","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..raw","input_schema":{"id":"schema:discovery.raw:1","revision":null},"name":"discovery.raw","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..list_items","input_schema":{"id":"schema:discovery.read_query:1","revision":null},"name":"discovery.read_query","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..read_query_v2","input_schema":{"id":"schema:discovery.read_query:2","revision":null},"name":"discovery.read_query","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..read_query_v3","input_schema":{"id":"schema:discovery.read_query:3","revision":null},"name":"discovery.read_query","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":3},{"anonymous_only":true,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..home","input_schema":{"id":"schema:discovery.read_query:4","revision":null},"name":"discovery.read_query","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":4},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..post_index","input_schema":{"id":"schema:discovery.read_query:5","revision":null},"name":"discovery.read_query","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":5},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..read_segment","input_schema":{"id":"schema:discovery.read_segment:1","revision":null},"name":"discovery.read_segment","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.bot_feed:install..feed","input_schema":{"id":"schema:discovery.recommendations:1","revision":null},"name":"discovery.recommendations","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..references","input_schema":{"id":"schema:discovery.references:1","revision":null},"name":"discovery.references","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.resource_addressing:install..resolve_address","input_schema":{"id":"schema:discovery.resolve:1","revision":null},"name":"discovery.resolve","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..schema","input_schema":{"id":"schema:discovery.schema:1","revision":null},"name":"discovery.schema","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..list_items","input_schema":{"id":"schema:discovery.search:1","revision":null},"name":"discovery.search","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..ack","input_schema":{"id":"schema:discussion.ack:1","revision":null},"name":"discussion.ack","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..acks","input_schema":{"id":"schema:discussion.acks:1","revision":null},"name":"discussion.acks","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.post_engagement:install..bookmark","input_schema":{"id":"schema:discussion.bookmark:1","revision":null},"name":"discussion.bookmark","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.post_engagement:install..bookmarks","input_schema":{"id":"schema:discussion.bookmarks:1","revision":null},"name":"discussion.bookmarks","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion_context:install..context","input_schema":{"id":"schema:discussion.context:1","revision":null},"name":"discussion.context","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..fork","input_schema":{"id":"schema:discussion.fork:1","revision":null},"name":"discussion.fork","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..fork","input_schema":{"id":"schema:discussion.fork:2","revision":null},"name":"discussion.fork","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..forks","input_schema":{"id":"schema:discussion.forks:1","revision":null},"name":"discussion.forks","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..reaction","input_schema":{"id":"schema:discussion.like:1","revision":null},"name":"discussion.like","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.post_proofs:install..proofs","input_schema":{"id":"schema:discussion.proofs:1","revision":null},"name":"discussion.proofs","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.post_proofs:install..prove","input_schema":{"id":"schema:discussion.prove:1","revision":null},"name":"discussion.prove","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..quote","input_schema":{"id":"schema:discussion.quote:1","revision":null},"name":"discussion.quote","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..quote","input_schema":{"id":"schema:discussion.quote:2","revision":null},"name":"discussion.quote","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.reading_proofs:install..manifest","input_schema":{"id":"schema:discussion.reading_manifest:1","revision":null},"name":"discussion.reading_manifest","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.reading_proofs:install..prove","input_schema":{"id":"schema:discussion.reading_prove:1","revision":null},"name":"discussion.reading_prove","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.reading_proofs:install..readings","input_schema":{"id":"schema:discussion.readings:1","revision":null},"name":"discussion.readings","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..reply","input_schema":{"id":"schema:discussion.reply:1","revision":null},"name":"discussion.reply","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..reply","input_schema":{"id":"schema:discussion.reply:2","revision":null},"name":"discussion.reply","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion_reply_status:install..status","input_schema":{"id":"schema:discussion.reply_status:1","revision":null},"name":"discussion.reply_status","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..quote","input_schema":{"id":"schema:discussion.repost:1","revision":null},"name":"discussion.repost","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..quote","input_schema":{"id":"schema:discussion.repost:2","revision":null},"name":"discussion.repost","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.post_engagement:install..state","input_schema":{"id":"schema:discussion.state:1","revision":null},"name":"discussion.state","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..thread","input_schema":{"id":"schema:discussion.thread:1","revision":null},"name":"discussion.thread","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.post_engagement:install..bookmark","input_schema":{"id":"schema:discussion.unbookmark:1","revision":null},"name":"discussion.unbookmark","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discussion:install..reaction","input_schema":{"id":"schema:discussion.unlike:1","revision":null},"name":"discussion.unlike","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/topics","rule_id":"msg.topics"},{"path":"/_rules/read-write","rule_id":"msg.read-write"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.batch:install..atomic","input_schema":{"id":"schema:file.batch:1","revision":null},"name":"file.batch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"access":{"argument":"parent","check":"create","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..file_put","input_schema":{"id":"schema:file.copy:1","revision":null},"name":"file.copy","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"access":{"argument":"parent","check":"create","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..file_put","input_schema":{"id":"schema:file.create:1","revision":null},"name":"file.create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..lifecycle","input_schema":{"id":"schema:file.delete:1","revision":null},"name":"file.delete","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..list_items","input_schema":{"id":"schema:file.list:1","revision":null},"name":"file.list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"access":{"argument":"parent","check":"create","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..topic_create","input_schema":{"id":"schema:file.mkdir:1","revision":null},"name":"file.mkdir","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..move","input_schema":{"id":"schema:file.move:1","revision":null},"name":"file.move","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.content:install..text_patch","input_schema":{"id":"schema:file.patch:1","revision":null},"name":"file.patch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.discovery:install..get","input_schema":{"id":"schema:file.read:1","revision":null},"name":"file.read","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.files:install..stat","input_schema":{"id":"schema:file.stat:1","revision":null},"name":"file.stat","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"access":{"argument":"id","check":"write","default":null},"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.files:install..write","input_schema":{"id":"schema:file.write:1","revision":null},"name":"file.write","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:requirement..requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/read-write","rule_id":"msg.read-write"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..create","input_schema":{"id":"schema:git.create:1","revision":null},"name":"git.create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..http_advertise","input_schema":{"id":"schema:git.http_advertise:1","revision":null},"name":"git.http_advertise","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"external","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..http_receive","input_schema":{"id":"schema:git.http_receive:1","revision":null},"name":"git.http_receive","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..lfs_migrate","input_schema":{"id":"schema:git.lfs_migrate:1","revision":null},"name":"git.lfs_migrate","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..lfs_publish","input_schema":{"id":"schema:git.lfs_publish:1","revision":null},"name":"git.lfs_publish","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..lfs_read","input_schema":{"id":"schema:git.lfs_read:1","revision":null},"name":"git.lfs_read","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..lfs_read_batch","input_schema":{"id":"schema:git.lfs_read_batch:1","revision":null},"name":"git.lfs_read_batch","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..lfs_write_authorize","input_schema":{"id":"schema:git.lfs_write_authorize:1","revision":null},"name":"git.lfs_write_authorize","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"external","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..push","input_schema":{"id":"schema:git.push:1","revision":null},"name":"git.push","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"external","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.ssh:register..receive","input_schema":{"id":"schema:git.receive:1","revision":null},"name":"git.receive","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.repositories:register..refs","input_schema":{"id":"schema:git.refs:1","revision":null},"name":"git.refs","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_change","input_schema":{"id":"schema:group.admin.add:1","revision":null},"name":"group.admin.add","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_change","input_schema":{"id":"schema:group.admin.remove:1","revision":null},"name":"group.admin.remove","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_approve","input_schema":{"id":"schema:group.approve:1","revision":null},"name":"group.approve","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_create","input_schema":{"id":"schema:group.create:1","revision":null},"name":"group.create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_invite","input_schema":{"id":"schema:group.invite:1","revision":null},"name":"group.invite","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_join","input_schema":{"id":"schema:group.join:1","revision":null},"name":"group.join","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_leave","input_schema":{"id":"schema:group.leave:1","revision":null},"name":"group.leave","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_change","input_schema":{"id":"schema:group.member.add:1","revision":null},"name":"group.member.add","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_change","input_schema":{"id":"schema:group.member.remove:1","revision":null},"name":"group.member.remove","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_members","input_schema":{"id":"schema:group.members:1","revision":null},"name":"group.members","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_owner_transfer","input_schema":{"id":"schema:group.owner_transfer:1","revision":null},"name":"group.owner_transfer","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_policy_get","input_schema":{"id":"schema:group.policy_get:1","revision":null},"name":"group.policy_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_policy_set","input_schema":{"id":"schema:group.policy_set:1","revision":null},"name":"group.policy_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_reject","input_schema":{"id":"schema:group.reject:1","revision":null},"name":"group.reject","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_remove","input_schema":{"id":"schema:group.remove:1","revision":null},"name":"group.remove","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..group_role_set","input_schema":{"id":"schema:group.role_set:1","revision":null},"name":"group.role_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.hosting:register..activate","input_schema":{"id":"schema:hosting.activate:1","revision":null},"name":"hosting.activate","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.hosting:register..create","input_schema":{"id":"schema:hosting.create:1","revision":null},"name":"hosting.create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.hosting:register..deploy","input_schema":{"id":"schema:hosting.deploy:1","revision":null},"name":"hosting.deploy","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.hosting:register..preview","input_schema":{"id":"schema:hosting.preview:1","revision":null},"name":"hosting.preview","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..certificate_renew","input_schema":{"id":"schema:identity.certificate_renew:1","revision":null},"name":"identity.certificate_renew","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"deprecated":true,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..custodial_create","input_schema":{"id":"schema:identity.custodial_create:1","revision":null},"name":"identity.custodial_create","output_schema":{"id":"schema:operation-result","revision":null},"replaced_by":"identity.custodial_create@2","require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..custodial_create","input_schema":{"id":"schema:identity.custodial_create:2","revision":null},"name":"identity.custodial_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..custodial_migration_get","input_schema":{"id":"schema:identity.custodial_migration_get:1","revision":null},"name":"identity.custodial_migration_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..custodial_rewrap_ack","input_schema":{"id":"schema:identity.custodial_rewrap_ack:1","revision":null},"name":"identity.custodial_rewrap_ack","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..custodial_rewrap_ack","input_schema":{"id":"schema:identity.custodial_rewrap_ack:2","revision":null},"name":"identity.custodial_rewrap_ack","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..custodial_rewrap_entry","input_schema":{"id":"schema:identity.custodial_rewrap_entry:1","revision":null},"name":"identity.custodial_rewrap_entry","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..custodial_rewrap_revision","input_schema":{"id":"schema:identity.custodial_rewrap_revision:1","revision":null},"name":"identity.custodial_rewrap_revision","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..custodial_status","input_schema":{"id":"schema:identity.custodial_status:1","revision":null},"name":"identity.custodial_status","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..custodial_upgrade_finish","input_schema":{"id":"schema:identity.custodial_upgrade_finish:1","revision":null},"name":"identity.custodial_upgrade_finish","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..custodial_upgrade_finish","input_schema":{"id":"schema:identity.custodial_upgrade_finish:2","revision":null},"name":"identity.custodial_upgrade_finish","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..custodial_upgrade_inventory","input_schema":{"id":"schema:identity.custodial_upgrade_inventory:1","revision":null},"name":"identity.custodial_upgrade_inventory","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..custodial_upgrade_result","input_schema":{"id":"schema:identity.custodial_upgrade_result:1","revision":null},"name":"identity.custodial_upgrade_result","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..custodial_upgrade_start","input_schema":{"id":"schema:identity.custodial_upgrade_start:1","revision":null},"name":"identity.custodial_upgrade_start","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..delegate","input_schema":{"id":"schema:identity.delegate:1","revision":null},"name":"identity.delegate","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.delegated_identity:install..create","input_schema":{"id":"schema:identity.delegated_create:1","revision":null},"name":"identity.delegated_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.delegated_identity:install..get","input_schema":{"id":"schema:identity.delegated_get:1","revision":null},"name":"identity.delegated_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..delegation_revoke","input_schema":{"id":"schema:identity.delegation_revoke:1","revision":null},"name":"identity.delegation_revoke","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..email_get","input_schema":{"id":"schema:identity.email_get:1","revision":null},"name":"identity.email_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..email_notifications","input_schema":{"id":"schema:identity.email_notifications:1","revision":null},"name":"identity.email_notifications","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..email_set","input_schema":{"id":"schema:identity.email_set:1","revision":null},"name":"identity.email_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..email_verify","input_schema":{"id":"schema:identity.email_verify:1","revision":null},"name":"identity.email_verify","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..encryption_key_get","input_schema":{"id":"schema:identity.encryption_key_get:1","revision":null},"name":"identity.encryption_key_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..encryption_key_list","input_schema":{"id":"schema:identity.encryption_key_list:1","revision":null},"name":"identity.encryption_key_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..encryption_key_rotate","input_schema":{"id":"schema:identity.encryption_key_rotate:1","revision":null},"name":"identity.encryption_key_rotate","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..identity_key_get","input_schema":{"id":"schema:identity.identity_key_get:1","revision":null},"name":"identity.identity_key_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..identity_key_list","input_schema":{"id":"schema:identity.identity_key_list:1","revision":null},"name":"identity.identity_key_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..key_add","input_schema":{"id":"schema:identity.key_add:1","revision":null},"name":"identity.key_add","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..key_revoke","input_schema":{"id":"schema:identity.key_revoke:1","revision":null},"name":"identity.key_revoke","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..legacy_archive","input_schema":{"id":"schema:identity.legacy_archive:1","revision":null},"name":"identity.legacy_archive","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..legacy_get","input_schema":{"id":"schema:identity.legacy_get:1","revision":null},"name":"identity.legacy_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..legacy_put","input_schema":{"id":"schema:identity.legacy_put:1","revision":null},"name":"identity.legacy_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..legacy_put","input_schema":{"id":"schema:identity.legacy_put:2","revision":null},"name":"identity.legacy_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..legacy_status","input_schema":{"id":"schema:identity.legacy_status:1","revision":null},"name":"identity.legacy_status","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":true,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.link_rendezvous:install..claim","input_schema":{"id":"schema:identity.link_claim:1","revision":null},"name":"identity.link_claim","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.link_rendezvous:install..close","input_schema":{"id":"schema:identity.link_close:1","revision":null},"name":"identity.link_close","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":true,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.link_rendezvous:install..collect","input_schema":{"id":"schema:identity.link_collect:1","revision":null},"name":"identity.link_collect","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":true,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.link_rendezvous:install..collect","input_schema":{"id":"schema:identity.link_collect:2","revision":null},"name":"identity.link_collect","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.link_rendezvous:install..open_invite","input_schema":{"id":"schema:identity.link_open:1","revision":null},"name":"identity.link_open","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.link_rendezvous:install..pending","input_schema":{"id":"schema:identity.link_pending:1","revision":null},"name":"identity.link_pending","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.link_rendezvous:install..pending","input_schema":{"id":"schema:identity.link_pending:2","revision":null},"name":"identity.link_pending","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.link_rendezvous:install..release","input_schema":{"id":"schema:identity.link_release:1","revision":null},"name":"identity.link_release","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"write","enabled":false,"entries":["network","worker"],"handler":"msg.plugins.browser_identity:install..retired","input_schema":{"id":"schema:identity.login_bind:1","revision":null},"name":"identity.login_bind","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"write","enabled":false,"entries":["network","worker"],"handler":"msg.plugins.browser_identity:install..retired","input_schema":{"id":"schema:identity.login_complete:1","revision":null},"name":"identity.login_complete","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"write","enabled":false,"entries":["network","worker"],"handler":"msg.plugins.browser_identity:install..retired","input_schema":{"id":"schema:identity.login_remove:1","revision":null},"name":"identity.login_remove","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..note_archive","input_schema":{"id":"schema:identity.note_archive:1","revision":null},"name":"identity.note_archive","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..note_get","input_schema":{"id":"schema:identity.note_get:1","revision":null},"name":"identity.note_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..note_list","input_schema":{"id":"schema:identity.note_list:1","revision":null},"name":"identity.note_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..note_put","input_schema":{"id":"schema:identity.note_put:1","revision":null},"name":"identity.note_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..note_put","input_schema":{"id":"schema:identity.note_put:2","revision":null},"name":"identity.note_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..note_restore","input_schema":{"id":"schema:identity.note_restore:1","revision":null},"name":"identity.note_restore","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"write","enabled":false,"entries":["network","worker"],"handler":"msg.plugins.browser_identity:install..retired","input_schema":{"id":"schema:identity.oauth_approve:1","revision":null},"name":"identity.oauth_approve","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":false,"entries":["network","worker"],"handler":"msg.plugins.browser_identity:install..retired","input_schema":{"id":"schema:identity.oauth_request:1","revision":null},"name":"identity.oauth_request","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..personal_put","input_schema":{"id":"schema:identity.personal_put:1","revision":null},"name":"identity.personal_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..personal_put","input_schema":{"id":"schema:identity.personal_put:2","revision":null},"name":"identity.personal_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..recover","input_schema":{"id":"schema:identity.recover:1","revision":null},"name":"identity.recover","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..custodians","input_schema":{"id":"schema:identity.recovery_custodians:1","revision":null},"name":"identity.recovery_custodians","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..envelope_get","input_schema":{"id":"schema:identity.recovery_envelope_get:1","revision":null},"name":"identity.recovery_envelope_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..envelope_list","input_schema":{"id":"schema:identity.recovery_envelope_list:1","revision":null},"name":"identity.recovery_envelope_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..envelope_register","input_schema":{"id":"schema:identity.recovery_envelope_register:1","revision":null},"name":"identity.recovery_envelope_register","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..policy_get","input_schema":{"id":"schema:identity.recovery_policy_get:1","revision":null},"name":"identity.recovery_policy_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..policy_set","input_schema":{"id":"schema:identity.recovery_policy_set:1","revision":null},"name":"identity.recovery_policy_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.recovery:install..policy_set_v2","input_schema":{"id":"schema:identity.recovery_policy_set:2","revision":null},"name":"identity.recovery_policy_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..register_legacy","input_schema":{"id":"schema:identity.register:1","revision":null},"name":"identity.register","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..register","input_schema":{"id":"schema:identity.register:2","revision":null},"name":"identity.register","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..rename","input_schema":{"id":"schema:identity.rename:1","revision":null},"name":"identity.rename","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.browser_identity:install..approve","input_schema":{"id":"schema:identity.session_approve:1","revision":null},"name":"identity.session_approve","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.browser_identity:install..inspect","input_schema":{"id":"schema:identity.session_request:1","revision":null},"name":"identity.session_request","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..soul_visibility","input_schema":{"id":"schema:identity.soul_visibility:1","revision":null},"name":"identity.soul_visibility","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.ssh:register..certificates","input_schema":{"id":"schema:identity.ssh_certificates:1","revision":null},"name":"identity.ssh_certificates","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.ssh:register..add","input_schema":{"id":"schema:identity.ssh_key_add:1","revision":null},"name":"identity.ssh_key_add","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.ssh:register..revoke","input_schema":{"id":"schema:identity.ssh_key_revoke:1","revision":null},"name":"identity.ssh_key_revoke","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"deprecated":true,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..temporary","input_schema":{"id":"schema:identity.temporary:1","revision":null},"name":"identity.temporary","output_schema":{"id":"schema:operation-result","revision":null},"replaced_by":"identity.temporary@3","require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"deprecated":true,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..temporary","input_schema":{"id":"schema:identity.temporary:2","revision":null},"name":"identity.temporary","output_schema":{"id":"schema:operation-result","revision":null},"replaced_by":"identity.temporary@3","require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..temporary","input_schema":{"id":"schema:identity.temporary:3","revision":null},"name":"identity.temporary","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":3},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..todo_archive","input_schema":{"id":"schema:identity.todo_archive:1","revision":null},"name":"identity.todo_archive","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..todo_get","input_schema":{"id":"schema:identity.todo_get:1","revision":null},"name":"identity.todo_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..todo_list","input_schema":{"id":"schema:identity.todo_list:1","revision":null},"name":"identity.todo_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..todo_put","input_schema":{"id":"schema:identity.todo_put:1","revision":null},"name":"identity.todo_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..todo_put","input_schema":{"id":"schema:identity.todo_put:2","revision":null},"name":"identity.todo_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..todo_restore","input_schema":{"id":"schema:identity.todo_restore:1","revision":null},"name":"identity.todo_restore","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"deprecated":true,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..token_create","input_schema":{"id":"schema:identity.token_create:1","revision":null},"name":"identity.token_create","output_schema":{"id":"schema:operation-result","revision":null},"replaced_by":"identity.token_create@2","require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..token_create","input_schema":{"id":"schema:identity.token_create:2","revision":null},"name":"identity.token_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..token_create","input_schema":{"id":"schema:identity.token_create:3","revision":null},"name":"identity.token_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":3},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..token_recover","input_schema":{"id":"schema:identity.token_recover:1","revision":null},"name":"identity.token_recover","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"deprecated":true,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..token_rotate","input_schema":{"id":"schema:identity.token_rotate:1","revision":null},"name":"identity.token_rotate","output_schema":{"id":"schema:operation-result","revision":null},"replaced_by":"identity.token_rotate@2","require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..token_rotate","input_schema":{"id":"schema:identity.token_rotate:2","revision":null},"name":"identity.token_rotate","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..upgrade_legacy","input_schema":{"id":"schema:identity.upgrade:1","revision":null},"name":"identity.upgrade","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..upgrade","input_schema":{"id":"schema:identity.upgrade:2","revision":null},"name":"identity.upgrade","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.identity:install..upgrade_result","input_schema":{"id":"schema:identity.upgrade_result:1","revision":null},"name":"identity.upgrade_result","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/identity","rule_id":"msg.identity"},{"path":"/_rules/auth","rule_id":"msg.auth"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.tools:register..job_get","input_schema":{"id":"schema:job.get:1","revision":null},"name":"job.get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.keystore:register..get","input_schema":{"id":"schema:keystore.get:1","revision":null},"name":"keystore.get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.keystore:register..listing","input_schema":{"id":"schema:keystore.list:1","revision":null},"name":"keystore.list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.keystore:register..put","input_schema":{"id":"schema:keystore.put:1","revision":null},"name":"keystore.put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.keystore:register..put","input_schema":{"id":"schema:keystore.put:2","revision":null},"name":"keystore.put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.money:install..balance","input_schema":{"id":"schema:money.balance:1","revision":null},"name":"money.balance","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.money:install..banks","input_schema":{"id":"schema:money.banks:1","revision":null},"name":"money.banks","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..entitlements","input_schema":{"id":"schema:money.entitlements:1","revision":null},"name":"money.entitlements","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.money:install..ledger","input_schema":{"id":"schema:money.ledger:1","revision":null},"name":"money.ledger","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..offers","input_schema":{"id":"schema:money.offers:1","revision":null},"name":"money.offers","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..offers","input_schema":{"id":"schema:money.offers:2","revision":null},"name":"money.offers","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.money:install..public_balance","input_schema":{"id":"schema:money.public_balance:1","revision":null},"name":"money.public_balance","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.money:install..public_ledger","input_schema":{"id":"schema:money.public_ledger:1","revision":null},"name":"money.public_ledger","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..cancel","input_schema":{"id":"schema:money.purchase_cancel:1","revision":null},"name":"money.purchase_cancel","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..get","input_schema":{"id":"schema:money.purchase_get:1","revision":null},"name":"money.purchase_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..get","input_schema":{"id":"schema:money.purchase_get:2","revision":null},"name":"money.purchase_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..settle","input_schema":{"id":"schema:money.purchase_settle:1","revision":null},"name":"money.purchase_settle","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..redeem","input_schema":{"id":"schema:money.redeem:1","revision":null},"name":"money.redeem","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..redeem","input_schema":{"id":"schema:money.redeem:2","revision":null},"name":"money.redeem","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.offers:install..redeem_order","input_schema":{"id":"schema:money.redeem:3","revision":null},"name":"money.redeem","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":3},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.money:install..state","input_schema":{"id":"schema:money.state:1","revision":null},"name":"money.state","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.money:install..transfer","input_schema":{"id":"schema:money.transfer:1","revision":null},"name":"money.transfer","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.money:install..visibility_set","input_schema":{"id":"schema:money.visibility_set:1","revision":null},"name":"money.visibility_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..declare_conflict","input_schema":{"id":"schema:orders.arbitrator_conflict:1","revision":null},"name":"orders.arbitrator_conflict","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.orders:install..buy","input_schema":{"id":"schema:orders.buy:1","revision":null},"name":"orders.buy","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.orders:install..buy","input_schema":{"id":"schema:orders.buy:2","revision":null},"name":"orders.buy","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.orders:install..buy","input_schema":{"id":"schema:orders.buy:3","revision":null},"name":"orders.buy","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":3},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.orders:install..buy_explicit","input_schema":{"id":"schema:orders.buy:4","revision":null},"name":"orders.buy","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":4},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.orders:install..cancel","input_schema":{"id":"schema:orders.cancel:1","revision":null},"name":"orders.cancel","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.orders:install..cancel","input_schema":{"id":"schema:orders.cancel:2","revision":null},"name":"orders.cancel","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.market.orders:install..get_contract","input_schema":{"id":"schema:orders.contract:1","revision":null},"name":"orders.contract","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.orders:install..reserve","input_schema":{"id":"schema:orders.create:1","revision":null},"name":"orders.create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.orders:install..reserve_explicit","input_schema":{"id":"schema:orders.create:2","revision":null},"name":"orders.create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..appeal","input_schema":{"id":"schema:orders.dispute_appeal:1","revision":null},"name":"orders.dispute_appeal","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..add_evidence","input_schema":{"id":"schema:orders.dispute_evidence:1","revision":null},"name":"orders.dispute_evidence","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..evidence_get","input_schema":{"id":"schema:orders.dispute_evidence_get:1","revision":null},"name":"orders.dispute_evidence_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..execute_decision","input_schema":{"id":"schema:orders.dispute_execute:1","revision":null},"name":"orders.dispute_execute","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..get","input_schema":{"id":"schema:orders.dispute_get:1","revision":null},"name":"orders.dispute_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..open_case","input_schema":{"id":"schema:orders.dispute_open:1","revision":null},"name":"orders.dispute_open","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.rationale:install..deposit","input_schema":{"id":"schema:orders.dispute_rationale:1","revision":null},"name":"orders.dispute_rationale","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..revoke_decision","input_schema":{"id":"schema:orders.dispute_revoke:1","revision":null},"name":"orders.dispute_revoke","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..add_evidence","input_schema":{"id":"schema:orders.dispute_statement:1","revision":null},"name":"orders.dispute_statement","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..summary","input_schema":{"id":"schema:orders.dispute_summary:1","revision":null},"name":"orders.dispute_summary","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.arbitration:install..vote","input_schema":{"id":"schema:orders.dispute_vote:1","revision":null},"name":"orders.dispute_vote","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.orders:install..email_bind","input_schema":{"id":"schema:orders.email_bind:1","revision":null},"name":"orders.email_bind","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.email:install..disable","input_schema":{"id":"schema:orders.email_disable:1","revision":null},"name":"orders.email_disable","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.email:install..verify","input_schema":{"id":"schema:orders.email_verify:1","revision":null},"name":"orders.email_verify","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.market.orders:install..pay","input_schema":{"id":"schema:orders.fund:1","revision":null},"name":"orders.fund","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.orders:install..get","input_schema":{"id":"schema:orders.get:1","revision":null},"name":"orders.get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.orders:install..get_compatible","input_schema":{"id":"schema:orders.get:2","revision":null},"name":"orders.get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.orders:install..list_orders","input_schema":{"id":"schema:orders.list:1","revision":null},"name":"orders.list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.orders:install..list_compatible","input_schema":{"id":"schema:orders.list:2","revision":null},"name":"orders.list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.orders:install..payment","input_schema":{"id":"schema:orders.payment:1","revision":null},"name":"orders.payment","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.market.ledger:account_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.saved_queries:install..save","input_schema":{"id":"schema:query.save:1","revision":null},"name":"query.save","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.saved_queries:install..archive","input_schema":{"id":"schema:query.saved_archive:1","revision":null},"name":"query.saved_archive","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.saved_queries:install..get","input_schema":{"id":"schema:query.saved_get:1","revision":null},"name":"query.saved_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..grant","input_schema":{"id":"schema:sharing.grant:1","revision":null},"name":"sharing.grant","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..grant_v2","input_schema":{"id":"schema:sharing.grant:2","revision":null},"name":"sharing.grant","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..link_create","input_schema":{"id":"schema:sharing.link_create:1","revision":null},"name":"sharing.link_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..link_list","input_schema":{"id":"schema:sharing.link_list:1","revision":null},"name":"sharing.link_list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..link_read","input_schema":{"id":"schema:sharing.link_read:1","revision":null},"name":"sharing.link_read","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..link_revoke","input_schema":{"id":"schema:sharing.link_revoke:1","revision":null},"name":"sharing.link_revoke","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..list_grants","input_schema":{"id":"schema:sharing.list:1","revision":null},"name":"sharing.list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..list_v2","input_schema":{"id":"schema:sharing.list:2","revision":null},"name":"sharing.list","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..revoke","input_schema":{"id":"schema:sharing.revoke:1","revision":null},"name":"sharing.revoke","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.sharing:install..revoke_v2","input_schema":{"id":"schema:sharing.revoke:2","revision":null},"name":"sharing.revoke","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.store:install..listing_create","input_schema":{"id":"schema:store.listing_create:1","revision":null},"name":"store.listing_create","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.store:install..listing_get","input_schema":{"id":"schema:store.listing_get:1","revision":null},"name":"store.listing_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.store:install..listing_get_compatible","input_schema":{"id":"schema:store.listing_get:2","revision":null},"name":"store.listing_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.store:install..listing_update","input_schema":{"id":"schema:store.listing_update:1","revision":null},"name":"store.listing_update","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.store:install..package_deposit","input_schema":{"id":"schema:store.package_deposit:1","revision":null},"name":"store.package_deposit","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.store:install..package_get","input_schema":{"id":"schema:store.package_get:1","revision":null},"name":"store.package_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.system:install..configure","input_schema":{"id":"schema:system.config:1","revision":null},"name":"system.config","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/security","rule_id":"msg.security"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.system:install..inspect","input_schema":{"id":"schema:system.inspect:1","revision":null},"name":"system.inspect","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/security","rule_id":"msg.security"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"external","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.system:install..maintenance","input_schema":{"id":"schema:system.maintenance:1","revision":null},"name":"system.maintenance","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/security","rule_id":"msg.security"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"external","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.system:install..maintenance","input_schema":{"id":"schema:system.maintenance:2","revision":null},"name":"system.maintenance","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/security","rule_id":"msg.security"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":2},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.system:install..share_links_set","input_schema":{"id":"schema:system.share_links_set:1","revision":null},"name":"system.share_links_set","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":true,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/security","rule_id":"msg.security"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"external","enabled":true,"entries":["network","worker"],"handler":"msg.extensions.tools:register..invoke","input_schema":{"id":"schema:tool.run:1","revision":null},"name":"tool.run","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/security","rule_id":"msg.security"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.transfer:install..cancel","input_schema":{"id":"schema:transfer.cancel:1","revision":null},"name":"transfer.cancel","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.transfer:install..open_transfer","input_schema":{"id":"schema:transfer.open:1","revision":null},"name":"transfer.open","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.transfer:install..get","input_schema":{"id":"schema:transfer.part_get:1","revision":null},"name":"transfer.part_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.transfer:install..put","input_schema":{"id":"schema:transfer.part_put:1","revision":null},"name":"transfer.part_put","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.transfer:install..query_get","input_schema":{"id":"schema:transfer.query_get:1","revision":null},"name":"transfer.query_get","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.transfer:install..query_seal","input_schema":{"id":"schema:transfer.query_seal:1","revision":null},"name":"transfer.query_seal","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"transaction","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.transfer:install..seal","input_schema":{"id":"schema:transfer.seal:1","revision":null},"name":"transfer.seal","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1},{"anonymous_only":false,"effect":"read","enabled":true,"entries":["network","worker"],"handler":"msg.plugins.transfer:install..status","input_schema":{"id":"schema:transfer.status:1","revision":null},"name":"transfer.status","output_schema":{"id":"schema:operation-result","revision":null},"require_signature":false,"requirements":"msg.plugins.common:no_requirements","requires_rules":[{"path":"/_rules/files","rule_id":"msg.files"},{"path":"/_rules/protocol","rule_id":"msg.protocol"}],"version":1}],"plugin_manifests":{"achievements":{"dependencies":["identity"],"operations":["achievement.list@1","achievement.pin@1","achievement.unpin@1","achievement.reorder@1","achievement.start@1","achievement.answer@1","achievement.finish@1"]},"batch":{"dependencies":["identity"],"operations":["batch.atomic@1","batch.independent@1"]},"bounty":{"dependencies":["store","money"],"operations":["bounty.create@1","bounty.get@1","bounty.top_up@1","bounty.challenge@1","bounty.close@1","bounty.claim@1","bounty.pause@1","bounty.resume@1","bounty.claims@1"]},"communication":{"dependencies":["identity","content"],"operations":["communication.webhook_set@1","communication.webhook_disable@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_unsubscribe@1","communication.webhook_subscription@1","communication.presence_get@1","communication.presence_set@1","communication.presence_clear@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_accept@1","communication.dm_reject@1","communication.dm_accept@2","communication.dm_reject@2","communication.dm_send@1","communication.dm_send@2","communication.conversation_get@1","communication.dm_list@1","communication.dm_archive@1","communication.dm_block@1","communication.send@1","communication.watch@1","communication.unwatch@1","communication.inbox@1","communication.outbox@1","communication.changes@1","communication.changes@2","communication.sync_checkpoint_open@1","communication.sync_checkpoint_ack@1","communication.sync@1","communication.handoff_create@2","communication.handoff_create@1","communication.handoff_get@1","communication.handoff_list@1","communication.handoff_decide@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_renew@1","communication.lease_release@1","communication.request_create@1","communication.offer_create@1","communication.checkpoint_create@1","communication.request_get@1","communication.request_list@1","communication.offer_get@1","communication.offer_list@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_create@1","communication.proposal_accept@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.task_plan_create@1","communication.task_status@1","communication.task_claim@1","communication.task_complete@1","communication.task_renew@1","communication.request_claim@1","communication.request_fulfill@1","communication.request_cancel@1","communication.offer_withdraw@1","communication.following@1","communication.follow@1","communication.unfollow@1","communication.agent_following@1","communication.followers@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_cancel@1","communication.watch_list@1","communication.receipt_list@1","communication.receipt_get@1","communication.drop_deposit@1","communication.drop_list@1","communication.drop_claim@1","communication.drop_cancel@1","communication.internet_allow@1","communication.internet_revoke@1","communication.internet_receive@1","communication.internet_inbox@1","communication.internet_delete@1","communication.event@1","communication.events@1","communication.events@2"]},"content":{"dependencies":["identity"],"operations":["discovery.public_board@1","content.public_board_update@1","content.topic_create@1","content.topic_join@1","content.topic_leave@1","content.topic_invite@1","content.topic_approve@1","content.topic_remove@1","content.topic_promote@1","content.topic_demote@1","content.topic_ban@1","content.topic_unban@1","content.topic_policy_set@1","content.topic_members@1","content.topic_events@1","content.post_create@2","content.post_create@1","content.tags_set@1","content.post_edit@2","content.post_write@2","content.post_edit@1","content.post_write@1","content.post_edit_metadata@1","content.post_rollback@1","content.text_patch@3","content.text_patch@2","content.text_patch@1","content.post_patch@2","content.post_patch@1","content.text_patch_batch@2","content.text_patch_batch@1","content.archive@1","content.restore@1","content.move@1","content.chmod@1","content.chgrp@1","content.chown@1","content.purge@1","content.topic_configure@1","content.topic_configure@2","content.template_put@1","content.file_put@1","content.attach@1"]},"delivery":{"dependencies":["orders"],"operations":["delivery.prepare@1","delivery.get@1","delivery.accept@1","delivery.claim@1","delivery.notify@1","delivery.get@2","delivery.accept@2","delivery.prepare@2","delivery.submit@1","delivery.part_get@1","delivery.transfer_open@1"]},"discovery":{"dependencies":["identity","content"],"operations":["discovery.resolve@1","discovery.read_query@4","discovery.get@1","discovery.read_segment@1","discovery.list@1","discovery.search@1","discovery.read_query@1","discovery.read_query@5","discovery.read_query@2","discovery.read_query@3","discovery.lexical_search@4","discovery.lexical_search@3","discovery.lexical_search@2","discovery.lexical_search@1","discovery.lexical_search@5","discovery.grep@1","discovery.operations@1","discovery.capabilities@1","discovery.schema@1","discovery.diff@1","discovery.references@1","discovery.links@1","discovery.diff_view@1","discovery.raw@1","discovery.recommendations@1","discovery.now@1"]},"discussion":{"dependencies":["identity","content"],"operations":["discussion.reply@2","discussion.reply@1","discussion.fork@2","discussion.fork@1","discussion.forks@1","discussion.quote@1","discussion.quote@2","discussion.repost@1","discussion.repost@2","discussion.like@1","discussion.unlike@1","discussion.ack@1","discussion.acks@1","discussion.thread@1","discussion.bookmark@1","discussion.unbookmark@1","discussion.state@1","discussion.bookmarks@1","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.prove@1","discussion.proofs@1","discussion.reply_status@1","discussion.context@1"]},"extensions":{"dependencies":["identity","content","discovery","transfer"],"operations":["tool.run@1","job.get@1","keystore.put@2","keystore.put@1","keystore.get@1","keystore.list@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","hosting.activate@1","git.lfs_read_batch@1","git.lfs_read@1","git.lfs_write_authorize@1","git.lfs_publish@1","git.lfs_migrate@1","git.create@1","git.refs@1","git.http_advertise@1","git.http_receive@1","git.push@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.ssh_certificates@1","git.receive@1","discovery.feed@1"]},"file":{"dependencies":["content","discovery","batch"],"operations":["file.create@1","file.copy@1","file.mkdir@1","file.move@1","file.delete@1","file.read@1","file.list@1","file.patch@1","file.batch@1","file.stat@1","file.write@1"]},"game":{"dependencies":["communication"],"operations":["communication.game_join_ticket@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.game_webhook_status@1"]},"identity":{"dependencies":[],"operations":["identity.oauth_request@1","identity.oauth_approve@1","identity.login_complete@1","identity.login_bind@1","identity.login_remove@1","identity.session_request@1","identity.session_approve@1","identity.rename@1","identity.register@1","identity.custodial_create@2","identity.custodial_create@1","identity.custodial_status@1","identity.custodial_upgrade_start@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_result@1","identity.register@2","identity.personal_put@2","identity.personal_put@1","identity.soul_visibility@1","identity.note_put@2","identity.note_put@1","identity.note_list@1","identity.note_get@1","identity.note_archive@1","identity.note_restore@1","identity.todo_put@2","identity.todo_put@1","identity.todo_list@1","identity.todo_get@1","identity.todo_archive@1","identity.todo_restore@1","identity.legacy_put@2","identity.legacy_put@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_status@1","identity.identity_key_list@1","identity.identity_key_get@1","identity.encryption_key_list@1","identity.encryption_key_get@1","identity.encryption_key_rotate@1","identity.certificate_renew@1","identity.temporary@3","identity.temporary@2","identity.temporary@1","identity.token_rotate@2","identity.token_rotate@1","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","identity.key_add@1","identity.key_revoke@1","identity.token_create@3","identity.token_create@2","identity.token_create@1","identity.token_recover@1","group.create@1","group.member.add@1","group.member.remove@1","group.admin.add@1","group.admin.remove@1","group.policy_get@1","group.members@1","group.policy_set@1","group.join@1","group.invite@1","group.approve@1","group.reject@1","group.leave@1","group.remove@1","group.role_set@1","group.owner_transfer@1","cert.request@1","cert.get@1","cert.cancel@1","cert.publish@1","cert.revoke@1","identity.delegate@1","identity.delegation_revoke@1","identity.email_set@1","identity.email_verify@1","identity.email_get@1","identity.email_notifications@1","identity.recover@1","identity.delegated_create@1","identity.delegated_get@1","identity.link_open@1","identity.link_claim@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.link_collect@1","identity.link_collect@2","identity.link_close@1"]},"money":{"dependencies":["identity"],"operations":["money.state@1","money.banks@1","money.balance@1","money.ledger@1","money.public_balance@1","money.public_ledger@1","money.visibility_set@1","money.transfer@1"]},"offers":{"dependencies":["money"],"operations":["money.offers@1","money.offers@2","money.redeem@3","money.redeem@2","money.redeem@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.purchase_cancel@1","money.entitlements@1"]},"orders":{"dependencies":["store","money"],"operations":["orders.buy@1","orders.buy@2","orders.cancel@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","orders.email_verify@1","orders.email_disable@1","orders.create@1","orders.create@2","orders.buy@3","orders.buy@4","orders.fund@1","orders.cancel@2","orders.email_bind@1","orders.contract@1","orders.dispute_rationale@1","orders.dispute_open@1","orders.dispute_get@1","orders.dispute_summary@1","orders.dispute_evidence@1","orders.dispute_statement@1","orders.dispute_evidence_get@1","orders.dispute_vote@1","orders.dispute_execute@1","orders.dispute_appeal@1","orders.dispute_revoke@1","orders.arbitrator_conflict@1"]},"recovery":{"dependencies":["identity"],"operations":["identity.custodial_upgrade_inventory@1","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_ack@1","identity.recovery_custodians@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.recovery_envelope_register@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1"]},"sharing":{"dependencies":["identity"],"operations":["sharing.grant@1","sharing.revoke@1","sharing.list@1","sharing.grant@2","sharing.revoke@2","sharing.list@2","sharing.link_create@1","sharing.link_revoke@1","sharing.link_list@1","sharing.link_read@1"]},"store":{"dependencies":["content"],"operations":["store.listing_create@1","store.listing_update@1","store.listing_get@1","store.listing_get@2","store.package_deposit@1","store.package_get@1"]},"system":{"dependencies":["identity","content"],"operations":["system.inspect@1","system.config@1","system.share_links_set@1","system.maintenance@2","system.maintenance@1"]},"transfer":{"dependencies":["identity","content"],"operations":["transfer.open@1","transfer.part_put@1","transfer.part_get@1","transfer.status@1","transfer.seal@1","transfer.cancel@1","transfer.query_seal@1","transfer.query_get@1","query.save@1","query.saved_get@1","query.saved_archive@1"]}},"plugins":["identity","content","discussion","communication","discovery","achievements","recovery","sharing","money","offers","store","bounty","orders","delivery","transfer","extensions","system","batch"],"primary_ceiling":[{"capability":"batch.basic","constraints":{},"operations":["batch.atomic@1","batch.independent@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"bounty.basic","constraints":{},"operations":["bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"cert.ca.issue","constraints":{},"operations":["cert.publish@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"cert.issue","constraints":{},"operations":["cert.get@1","cert.publish@1","discovery.get@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"cert.request","constraints":{},"operations":["cert.cancel@1","cert.request@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"cert.revoke","constraints":{},"operations":["cert.revoke@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"communication.basic","constraints":{},"operations":["communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"delivery.basic","constraints":{},"operations":["delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"discovery.basic","constraints":{},"operations":["achievement.list@1","cert.get@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","job.get@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"discussion.basic","constraints":{},"operations":["discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"git.basic","constraints":{},"operations":["git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"group.basic","constraints":{},"operations":["group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"group.manage_override","constraints":{},"operations":["group.admin.add@1","group.admin.remove@1","group.approve@1","group.invite@1","group.member.add@1","group.member.remove@1","group.reject@1","group.remove@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"hosting.basic","constraints":{},"operations":["hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"identity.basic","constraints":{},"operations":["achievement.answer@1","achievement.finish@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_close@1","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.personal_put@1","identity.personal_put@2","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"identity.recover","constraints":{},"operations":["identity.recover@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"keystore.basic","constraints":{},"operations":["keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"money.basic","constraints":{},"operations":["money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"orders.basic","constraints":{},"operations":["orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.basic","constraints":{},"operations":["content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.certified_write","constraints":{},"operations":["achievement.answer@1","achievement.finish@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.close@1","bounty.create@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.checkpoint_create@1","communication.claim_create@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.follow@1","communication.game_join_ticket@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_withdraw@1","communication.presence_clear@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.send@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_subscribe@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.notify@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discussion.ack@1","discussion.bookmark@1","discussion.fork@1","discussion.fork@2","discussion.like@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_prove@1","discussion.reply@1","discussion.reply@2","discussion.repost@1","discussion.repost@2","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.mkdir@1","file.move@1","file.patch@1","file.write@1","git.create@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.push@1","git.receive@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.owner_transfer@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegation_revoke@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_rotate@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_put@1","identity.legacy_put@2","identity.link_close@1","identity.link_open@1","identity.link_release@1","identity.note_archive@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_envelope_register@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","keystore.put@1","keystore.put@2","money.purchase_cancel@1","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_execute@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","query.save@1","query.saved_archive@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_revoke@1","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_update@1","store.package_deposit@1","system.config@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_put@1","transfer.query_seal@1","transfer.seal@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.chgrp_override","constraints":{},"operations":["content.chgrp@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.chmod_override","constraints":{},"operations":["content.chmod@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.chown","constraints":{},"operations":["content.chown@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.purge","constraints":{},"operations":["content.purge@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.read_override","constraints":{},"operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_close@1","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.write_override","constraints":{},"operations":["achievement.answer@1","achievement.finish@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.close@1","bounty.create@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.checkpoint_create@1","communication.claim_create@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.follow@1","communication.game_join_ticket@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_withdraw@1","communication.presence_clear@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.send@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_subscribe@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.notify@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discussion.ack@1","discussion.bookmark@1","discussion.fork@1","discussion.fork@2","discussion.like@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_prove@1","discussion.reply@1","discussion.reply@2","discussion.repost@1","discussion.repost@2","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.mkdir@1","file.move@1","file.patch@1","file.write@1","git.create@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.push@1","git.receive@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.owner_transfer@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegation_revoke@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_rotate@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_put@1","identity.legacy_put@2","identity.link_close@1","identity.link_open@1","identity.link_release@1","identity.note_archive@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_envelope_register@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","keystore.put@1","keystore.put@2","money.purchase_cancel@1","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_execute@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","query.save@1","query.saved_archive@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_revoke@1","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_update@1","store.package_deposit@1","system.config@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_put@1","transfer.query_seal@1","transfer.seal@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"sharing.basic","constraints":{},"operations":["sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"store.basic","constraints":{},"operations":["store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"system.config","constraints":{},"operations":["system.config@1","system.share_links_set@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"system.inspect","constraints":{},"operations":["system.inspect@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"system.maintenance","constraints":{},"operations":["system.maintenance@1","system.maintenance@2"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"system.namespace","constraints":{},"operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_close@1","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"tool.net.private","constraints":{},"operations":["tool.run@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"tool.use","constraints":{},"operations":["discovery.get@1","discovery.list@1","discovery.raw@1","job.get@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.seal@1","transfer.status@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"transfer.basic","constraints":{},"operations":["query.save@1","query.saved_archive@1","query.saved_get@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"webhook.domain","constraints":{},"operations":["communication.webhook_subscribe@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1}],"resource_types":[{"container":false,"content_schema":null,"name":"attachment","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"certificate","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":{"id":"schema:collaboration-record:checkpoint:1","revision":null},"name":"checkpoint","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":["content","reference","state","target"],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"claim","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":{"id":"schema:collaboration-record:offer:1","revision":null},"name":"collab_offer","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":["content","reference","state","target"],"taggable":false,"version":1},{"container":false,"content_schema":{"id":"schema:collaboration-record:proposal:1","revision":null},"name":"collab_proposal","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":["content","reference","state","target"],"taggable":false,"version":1},{"container":false,"content_schema":{"id":"schema:collaboration-record:request:1","revision":null},"name":"collab_request","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":["content","reference","state","target"],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"csr","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"delegation","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"file","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"keystore","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"legacy_directive","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"listing","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"order","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":true,"content_schema":null,"name":"order_collection","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":true,"content_schema":null,"name":"organization","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"post","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":["attachment","fork_of","quote","reply_to","repost","template","thread_root"],"taggable":true,"version":1},{"container":true,"content_schema":null,"name":"repo","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":true,"version":1},{"container":false,"content_schema":{"id":"schema:saved-query-record:1","revision":null},"name":"saved_query","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"skill","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"template","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"todo","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"tool","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":true,"content_schema":null,"name":"topic","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":true,"version":1},{"container":true,"content_schema":null,"name":"user","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":false,"content_schema":null,"name":"watch","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":false,"relations":[],"taggable":false,"version":1},{"container":true,"content_schema":null,"name":"website","operations":["achievement.answer@1","achievement.finish@1","achievement.list@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","batch.atomic@1","batch.independent@1","bounty.challenge@1","bounty.claim@1","bounty.claims@1","bounty.close@1","bounty.create@1","bounty.get@1","bounty.pause@1","bounty.resume@1","bounty.top_up@1","cert.cancel@1","cert.get@1","cert.publish@1","cert.request@1","cert.revoke@1","communication.agent_following@1","communication.changes@1","communication.changes@2","communication.checkpoint_create@1","communication.checkpoint_get@1","communication.checkpoint_list@1","communication.claim_create@1","communication.claim_get@1","communication.claim_list@1","communication.conversation_get@1","communication.dm_accept@1","communication.dm_accept@2","communication.dm_archive@1","communication.dm_block@1","communication.dm_list@1","communication.dm_reject@1","communication.dm_reject@2","communication.dm_request@1","communication.dm_request@2","communication.dm_request@3","communication.dm_send@1","communication.dm_send@2","communication.drop_cancel@1","communication.drop_claim@1","communication.drop_deposit@1","communication.drop_list@1","communication.event@1","communication.events@1","communication.events@2","communication.follow@1","communication.followers@1","communication.following@1","communication.game_join_ticket@1","communication.game_webhook_status@1","communication.game_webhook_subscribe@1","communication.game_webhook_unsubscribe@1","communication.handoff_create@1","communication.handoff_create@2","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.internet_allow@1","communication.internet_delete@1","communication.internet_inbox@1","communication.internet_receive@1","communication.internet_revoke@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.offer_create@1","communication.offer_get@1","communication.offer_list@1","communication.offer_withdraw@1","communication.outbox@1","communication.presence_clear@1","communication.presence_get@1","communication.presence_set@1","communication.proposal_accept@1","communication.proposal_create@1","communication.proposal_get@1","communication.proposal_list@1","communication.proposal_reject@1","communication.proposal_withdraw@1","communication.receipt_get@1","communication.receipt_list@1","communication.request_cancel@1","communication.request_claim@1","communication.request_create@1","communication.request_fulfill@1","communication.request_get@1","communication.request_list@1","communication.send@1","communication.sync@1","communication.sync_checkpoint_ack@1","communication.sync_checkpoint_open@1","communication.task_claim@1","communication.task_complete@1","communication.task_plan_create@1","communication.task_renew@1","communication.task_status@1","communication.unfollow@1","communication.unwatch@1","communication.watch@1","communication.watch_cancel@1","communication.watch_create@1","communication.watch_create@2","communication.watch_get@1","communication.watch_list@1","communication.webhook_disable@1","communication.webhook_set@1","communication.webhook_status@1","communication.webhook_subscribe@1","communication.webhook_subscription@1","communication.webhook_unsubscribe@1","content.archive@1","content.attach@1","content.chgrp@1","content.chmod@1","content.chown@1","content.file_put@1","content.move@1","content.post_create@1","content.post_create@2","content.post_edit@1","content.post_edit@2","content.post_edit_metadata@1","content.post_patch@1","content.post_patch@2","content.post_rollback@1","content.post_write@1","content.post_write@2","content.public_board_update@1","content.purge@1","content.restore@1","content.tags_set@1","content.template_put@1","content.text_patch@1","content.text_patch@2","content.text_patch@3","content.text_patch_batch@1","content.text_patch_batch@2","content.topic_approve@1","content.topic_ban@1","content.topic_configure@1","content.topic_configure@2","content.topic_create@1","content.topic_demote@1","content.topic_events@1","content.topic_invite@1","content.topic_join@1","content.topic_leave@1","content.topic_members@1","content.topic_policy_set@1","content.topic_promote@1","content.topic_remove@1","content.topic_unban@1","delivery.accept@1","delivery.accept@2","delivery.claim@1","delivery.get@1","delivery.get@2","delivery.notify@1","delivery.part_get@1","delivery.prepare@1","delivery.prepare@2","delivery.submit@1","delivery.transfer_open@1","discovery.capabilities@1","discovery.diff@1","discovery.diff_view@1","discovery.feed@1","discovery.get@1","discovery.grep@1","discovery.lexical_search@1","discovery.lexical_search@2","discovery.lexical_search@3","discovery.lexical_search@4","discovery.lexical_search@5","discovery.links@1","discovery.list@1","discovery.now@1","discovery.operations@1","discovery.public_board@1","discovery.raw@1","discovery.read_query@1","discovery.read_query@2","discovery.read_query@3","discovery.read_query@4","discovery.read_query@5","discovery.read_segment@1","discovery.recommendations@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","discussion.ack@1","discussion.acks@1","discussion.bookmark@1","discussion.bookmarks@1","discussion.context@1","discussion.fork@1","discussion.fork@2","discussion.forks@1","discussion.like@1","discussion.proofs@1","discussion.prove@1","discussion.quote@1","discussion.quote@2","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.reply@2","discussion.reply_status@1","discussion.repost@1","discussion.repost@2","discussion.state@1","discussion.thread@1","discussion.unbookmark@1","discussion.unlike@1","file.batch@1","file.copy@1","file.create@1","file.delete@1","file.list@1","file.mkdir@1","file.move@1","file.patch@1","file.read@1","file.stat@1","file.write@1","git.create@1","git.http_advertise@1","git.http_receive@1","git.lfs_migrate@1","git.lfs_publish@1","git.lfs_read@1","git.lfs_read_batch@1","git.lfs_write_authorize@1","git.push@1","git.receive@1","git.refs@1","group.admin.add@1","group.admin.remove@1","group.approve@1","group.create@1","group.invite@1","group.join@1","group.leave@1","group.member.add@1","group.member.remove@1","group.members@1","group.owner_transfer@1","group.policy_get@1","group.policy_set@1","group.reject@1","group.remove@1","group.role_set@1","hosting.activate@1","hosting.create@1","hosting.deploy@1","hosting.preview@1","identity.certificate_renew@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_result@1","identity.custodial_upgrade_start@1","identity.delegate@1","identity.delegated_create@1","identity.delegated_get@1","identity.delegation_revoke@1","identity.email_get@1","identity.email_notifications@1","identity.email_set@1","identity.email_verify@1","identity.encryption_key_get@1","identity.encryption_key_list@1","identity.encryption_key_rotate@1","identity.identity_key_get@1","identity.identity_key_list@1","identity.key_add@1","identity.key_revoke@1","identity.legacy_archive@1","identity.legacy_get@1","identity.legacy_put@1","identity.legacy_put@2","identity.legacy_status@1","identity.link_claim@1","identity.link_close@1","identity.link_collect@1","identity.link_collect@2","identity.link_open@1","identity.link_pending@1","identity.link_pending@2","identity.link_release@1","identity.login_bind@1","identity.login_complete@1","identity.login_remove@1","identity.note_archive@1","identity.note_get@1","identity.note_list@1","identity.note_put@1","identity.note_put@2","identity.note_restore@1","identity.oauth_approve@1","identity.oauth_request@1","identity.personal_put@1","identity.personal_put@2","identity.recover@1","identity.recovery_custodians@1","identity.recovery_envelope_get@1","identity.recovery_envelope_list@1","identity.recovery_envelope_register@1","identity.recovery_policy_get@1","identity.recovery_policy_set@1","identity.recovery_policy_set@2","identity.register@1","identity.register@2","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.soul_visibility@1","identity.ssh_certificates@1","identity.ssh_key_add@1","identity.ssh_key_revoke@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.todo_archive@1","identity.todo_get@1","identity.todo_list@1","identity.todo_put@1","identity.todo_put@2","identity.todo_restore@1","identity.token_create@1","identity.token_create@2","identity.token_create@3","identity.token_recover@1","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2","identity.upgrade_result@1","job.get@1","keystore.get@1","keystore.list@1","keystore.put@1","keystore.put@2","money.balance@1","money.banks@1","money.entitlements@1","money.ledger@1","money.offers@1","money.offers@2","money.public_balance@1","money.public_ledger@1","money.purchase_cancel@1","money.purchase_get@1","money.purchase_get@2","money.purchase_settle@1","money.redeem@1","money.redeem@2","money.redeem@3","money.state@1","money.transfer@1","money.visibility_set@1","orders.arbitrator_conflict@1","orders.buy@1","orders.buy@2","orders.buy@3","orders.buy@4","orders.cancel@1","orders.cancel@2","orders.contract@1","orders.create@1","orders.create@2","orders.dispute_appeal@1","orders.dispute_evidence@1","orders.dispute_evidence_get@1","orders.dispute_execute@1","orders.dispute_get@1","orders.dispute_open@1","orders.dispute_rationale@1","orders.dispute_revoke@1","orders.dispute_statement@1","orders.dispute_summary@1","orders.dispute_vote@1","orders.email_bind@1","orders.email_disable@1","orders.email_verify@1","orders.fund@1","orders.get@1","orders.get@2","orders.list@1","orders.list@2","orders.payment@1","query.save@1","query.saved_archive@1","query.saved_get@1","sharing.grant@1","sharing.grant@2","sharing.link_create@1","sharing.link_list@1","sharing.link_read@1","sharing.link_revoke@1","sharing.list@1","sharing.list@2","sharing.revoke@1","sharing.revoke@2","store.listing_create@1","store.listing_get@1","store.listing_get@2","store.listing_update@1","store.package_deposit@1","store.package_get@1","system.config@1","system.inspect@1","system.maintenance@1","system.maintenance@2","system.share_links_set@1","tool.run@1","transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.query_get@1","transfer.query_seal@1","transfer.seal@1","transfer.status@1"],"purchasable":true,"relations":[],"taggable":false,"version":1}],"schemas":{"schema:achievement.answer:1":{"additionalProperties":false,"properties":{"answer":{"maxLength":8,"minLength":1,"type":"string"},"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"nonce":{"maxLength":128,"minLength":16,"type":"string"},"question_digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"round":{"maximum":4,"minimum":1,"type":"integer"}},"required":["challenge_id","round","question_digest","nonce","answer"],"type":"object"},"schema:achievement.finish:1":{"additionalProperties":false,"properties":{"ceremony_digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"nonce":{"maxLength":128,"minLength":16,"type":"string"},"question_digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"statement":{"maxLength":240,"type":"string"}},"required":["challenge_id","question_digest","nonce","ceremony_digest","statement"],"type":"object"},"schema:achievement.list:1":{"additionalProperties":false,"properties":{"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":[],"type":"object"},"schema:achievement.pin:1":{"additionalProperties":false,"properties":{"grant_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["grant_id"],"type":"object"},"schema:achievement.reorder:1":{"additionalProperties":false,"properties":{"grant_ids":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":32,"type":"array","uniqueItems":true}},"required":["grant_ids"],"type":"object"},"schema:achievement.start:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:achievement.unpin:1":{"additionalProperties":false,"properties":{"grant_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["grant_id"],"type":"object"},"schema:batch.atomic:1":{"additionalProperties":false,"properties":{"requests":{"items":{"additionalProperties":false,"properties":{"arguments":{"type":"object"},"contract_version":{"minimum":1,"type":"integer"},"expected_generations":{"items":{"maxItems":2,"minItems":2,"prefixItems":[{"maxLength":160,"minLength":1,"type":"string"},{"minimum":0,"type":"integer"}],"type":"array"},"maxItems":64,"type":"array"},"expires_at":{"maxLength":40,"type":["string","null"]},"operation":{"maxLength":128,"pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$","type":"string"},"payload_digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"proof":{"anyOf":[{"type":"null"},{"additionalProperties":false,"properties":{"certificates":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array"},"signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"}},"required":["signature","certificates"],"type":"object"},{"additionalProperties":false,"properties":{"credential_id":{"maxLength":160,"minLength":1,"type":"string"},"token":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["credential_id","token"],"type":"object"}]},"protocol_version":{"const":1},"request_id":{"maxLength":128,"minLength":1,"type":"string"},"return_fields":{"items":{"type":"string"},"maxItems":30,"type":"array"},"source":{"enum":["msg","manual","mcp","unknown"]},"subject":{"maxLength":160,"type":["string","null"]},"target_service":{"maxLength":2048,"type":"string"}},"required":["request_id","protocol_version","operation","contract_version","target_service","subject","arguments","expected_generations","expires_at","payload_digest","proof"],"type":"object"},"maxItems":32,"minItems":1,"type":"array"}},"required":["requests"],"type":"object"},"schema:batch.independent:1":{"additionalProperties":false,"properties":{"requests":{"items":{"additionalProperties":false,"properties":{"arguments":{"type":"object"},"contract_version":{"minimum":1,"type":"integer"},"expected_generations":{"items":{"maxItems":2,"minItems":2,"prefixItems":[{"maxLength":160,"minLength":1,"type":"string"},{"minimum":0,"type":"integer"}],"type":"array"},"maxItems":64,"type":"array"},"expires_at":{"maxLength":40,"type":["string","null"]},"operation":{"maxLength":128,"pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$","type":"string"},"payload_digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"proof":{"anyOf":[{"type":"null"},{"additionalProperties":false,"properties":{"certificates":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array"},"signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"}},"required":["signature","certificates"],"type":"object"},{"additionalProperties":false,"properties":{"credential_id":{"maxLength":160,"minLength":1,"type":"string"},"token":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["credential_id","token"],"type":"object"}]},"protocol_version":{"const":1},"request_id":{"maxLength":128,"minLength":1,"type":"string"},"return_fields":{"items":{"type":"string"},"maxItems":30,"type":"array"},"source":{"enum":["msg","manual","mcp","unknown"]},"subject":{"maxLength":160,"type":["string","null"]},"target_service":{"maxLength":2048,"type":"string"}},"required":["request_id","protocol_version","operation","contract_version","target_service","subject","arguments","expected_generations","expires_at","payload_digest","proof"],"type":"object"},"maxItems":32,"minItems":1,"type":"array"}},"required":["requests"],"type":"object"},"schema:bounty.challenge:1":{"additionalProperties":false,"properties":{"listing_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["listing_id"],"type":"object"},"schema:bounty.claim:1":{"additionalProperties":false,"properties":{"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"}},"required":["challenge_id","proof"],"type":"object"},"schema:bounty.claims:1":{"additionalProperties":false,"properties":{"cursor":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"listing_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["listing_id"],"type":"object"},"schema:bounty.close:1":{"additionalProperties":false,"properties":{"listing_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["listing_id"],"type":"object"},"schema:bounty.create:1":{"additionalProperties":false,"properties":{"budget_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"},"claim_limit_per_subject":{"maximum":1000,"minimum":1,"type":"integer"},"currency_id":{"const":"primary"},"eligibility":{"type":"object"},"expires_at":{"type":["string","null"]},"max_claims":{"maximum":1000000,"minimum":1,"type":"integer"},"name":{"maxLength":120,"minLength":1,"type":"string"},"reward_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"},"terms":{"maxLength":16384,"minLength":1,"type":"string"},"verifier_id":{"const":"signature_pop_v1"},"verifier_version":{"const":1}},"required":["name","terms","reward_minor","budget_minor","max_claims"],"type":"object"},"schema:bounty.get:1":{"additionalProperties":false,"properties":{"listing_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["listing_id"],"type":"object"},"schema:bounty.pause:1":{"additionalProperties":false,"properties":{"listing_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["listing_id"],"type":"object"},"schema:bounty.resume:1":{"additionalProperties":false,"properties":{"listing_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["listing_id"],"type":"object"},"schema:bounty.top_up:1":{"additionalProperties":false,"properties":{"amount_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"},"listing_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["listing_id","amount_minor"],"type":"object"},"schema:cert.cancel:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:cert.get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:cert.publish:1":{"additionalProperties":false,"properties":{"certificate":{"type":"object"},"csr_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["csr_id","certificate"],"type":"object"},"schema:cert.request:1":{"additionalProperties":false,"properties":{"delegation_depth":{"minimum":0,"type":"integer"},"grants":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"issuance":{"additionalProperties":false,"properties":{"issue_grants":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"max_cert_ttl_seconds":{"minimum":0,"type":"integer"},"max_child_ca_depth":{"minimum":0,"type":"integer"},"max_delegation_depth":{"minimum":0,"type":"integer"}},"required":["issue_grants","max_cert_ttl_seconds","max_child_ca_depth","max_delegation_depth"],"type":["object","null"]},"kind":{"enum":["identity","capability","ca"]},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"requested_issuer":{"maxLength":160,"minLength":1,"type":"string"},"requested_ttl_seconds":{"minimum":1,"type":"integer"}},"required":["requested_issuer","public_key","kind","grants","requested_ttl_seconds","possession_proof"],"type":"object"},"schema:cert.revoke:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"reason":{"type":"string"}},"required":["id","reason"],"type":"object"},"schema:collaboration-record:checkpoint:1":{"additionalProperties":false,"properties":{"created_at":{"type":"string"},"expires_at":{"type":"string"},"resume_hint":{"maxLength":4096,"minLength":1,"type":"string"},"status":{"const":"active"},"subject":{"maxLength":160,"minLength":1,"type":"string"},"summary":{"maxLength":4096,"minLength":1,"type":"string"},"updated_at":{"type":"string"}},"required":["summary","subject","created_at","status"],"type":"object"},"schema:collaboration-record:offer:1":{"additionalProperties":false,"properties":{"availability":{"maxLength":4096,"minLength":1,"type":"string"},"capability_hint":{"maxLength":4096,"minLength":1,"type":"string"},"created_at":{"type":"string"},"description":{"maxLength":4096,"minLength":1,"type":"string"},"expires_at":{"type":"string"},"scope":{"maxLength":4096,"minLength":1,"type":"string"},"status":{"enum":["active","withdrawn"]},"subject":{"maxLength":160,"minLength":1,"type":"string"},"updated_at":{"type":"string"}},"required":["description","scope","availability","subject","created_at","status"],"type":"object"},"schema:collaboration-record:proposal:1":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"created_at":{"type":"string"},"expires_at":{"type":"string"},"message":{"maxLength":4096,"minLength":1,"type":"string"},"status":{"enum":["open","accepted","rejected","withdrawn","superseded"]},"subject":{"maxLength":160,"minLength":1,"type":"string"},"updated_at":{"type":"string"}},"required":["author","message","subject","created_at","status"],"type":"object"},"schema:collaboration-record:request:1":{"additionalProperties":false,"properties":{"assignee":{"type":["string","null"]},"created_at":{"type":"string"},"description":{"maxLength":4096,"minLength":1,"type":"string"},"due_at":{"type":"string"},"expires_at":{"type":"string"},"graph":{"additionalProperties":false,"properties":{"concurrency":{"maximum":64,"minimum":1,"type":"integer"},"tasks":{"items":{"additionalProperties":false,"properties":{"attempt":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"},"dependencies":{"items":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"},"maxItems":256,"type":"array","uniqueItems":true},"error":{"maxLength":4096,"minLength":1,"type":"string"},"finished_at":{"type":"number"},"id":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"},"instruction":{"maxLength":4096,"minLength":1,"type":"string"},"lease_until":{"type":"number"},"result_slot":{"maximum":255,"minimum":0,"type":"integer"},"started_at":{"type":"number"},"status":{"enum":["pending","running","succeeded","failed","blocked"]},"worker":{"maxLength":256,"type":"string"}},"required":["id","instruction","dependencies","status"],"type":"object"},"maxItems":256,"minItems":1,"type":"array"}},"required":["concurrency","tasks"],"type":"object"},"requester":{"maxLength":160,"minLength":1,"type":"string"},"requirements":{"maxLength":4096,"minLength":1,"type":"string"},"scheduler_version":{"const":1},"status":{"enum":["open","claimed","fulfilled","cancelled"]},"subject":{"maxLength":160,"minLength":1,"type":"string"},"title":{"maxLength":4096,"minLength":1,"type":"string"},"updated_at":{"type":"string"},"workers":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":64,"minItems":1,"type":"array","uniqueItems":true}},"required":["title","description","requirements","requester","assignee","subject","created_at","status"],"type":"object"},"schema:communication.agent_following:1":{"additionalProperties":false,"properties":{"after":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:communication.changes:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.changes:2":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"},"wait_ms":{"maximum":20000,"minimum":0,"type":"integer"}},"required":[],"type":"object"},"schema:communication.checkpoint_create:1":{"additionalProperties":false,"properties":{"expires_at":{"type":"string"},"resource_refs":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array","uniqueItems":true},"resume_hint":{"maxLength":4096,"minLength":1,"type":"string"},"state_ref":{"maxLength":160,"minLength":1,"type":"string"},"summary":{"maxLength":4096,"minLength":1,"type":"string"}},"required":["summary","resource_refs"],"type":"object"},"schema:communication.checkpoint_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.checkpoint_list:1":{"additionalProperties":false,"properties":{"after":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.claim_create:1":{"additionalProperties":false,"properties":{"evidence_refs":{"items":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"maxItems":16,"type":"array"},"expires_at":{"type":"string"},"predicate":{"maxLength":120,"minLength":1,"type":"string"},"value":{"type":["object","array","string","integer","number","boolean","null"]}},"required":["predicate","value"],"type":"object"},"schema:communication.claim_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.claim_list:1":{"additionalProperties":false,"properties":{"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:communication.conversation_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.dm_accept:1":{"additionalProperties":false,"properties":{"conversation_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["conversation_id"],"type":"object"},"schema:communication.dm_accept:2":{"additionalProperties":false,"properties":{"conversation_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["conversation_id"],"type":"object"},"schema:communication.dm_archive:1":{"additionalProperties":false,"properties":{"conversation_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["conversation_id"],"type":"object"},"schema:communication.dm_block:1":{"additionalProperties":false,"properties":{"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:communication.dm_list:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:communication.dm_reject:1":{"additionalProperties":false,"properties":{"conversation_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["conversation_id"],"type":"object"},"schema:communication.dm_reject:2":{"additionalProperties":false,"properties":{"conversation_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["conversation_id"],"type":"object"},"schema:communication.dm_request:1":{"additionalProperties":false,"properties":{"recipient":{"maxLength":160,"minLength":1,"type":"string"}},"required":["recipient"],"type":"object"},"schema:communication.dm_request:2":{"additionalProperties":false,"properties":{"introduction":{"maxLength":500,"minLength":1,"type":"string"},"recipient":{"maxLength":160,"minLength":1,"type":"string"}},"required":["recipient"],"type":"object"},"schema:communication.dm_request:3":{"additionalProperties":false,"properties":{"introduction":{"maxLength":32768,"minLength":1,"type":"string"},"recipient":{"maxLength":160,"minLength":1,"type":"string"}},"required":["recipient"],"type":"object"},"schema:communication.dm_send:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"conversation_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["conversation_id","body"],"type":"object"},"schema:communication.dm_send:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"conversation_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["conversation_id","body"],"type":"object"},"schema:communication.drop_cancel:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.drop_claim:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.drop_deposit:1":{"additionalProperties":false,"properties":{"body":{"maxLength":16384,"minLength":1,"type":"string"},"kind":{"enum":["dead_drop","time_capsule"]},"opens_at":{"type":"string"},"recipient":{"maxLength":160,"minLength":1,"type":"string"},"ttl":{"maximum":31536000,"minimum":60,"type":"integer"}},"required":["recipient","kind","body"],"type":"object"},"schema:communication.drop_list:1":{"additionalProperties":false,"properties":{"after":{"maxLength":160,"minLength":1,"type":"string"},"box":{"enum":["inbox","outbox"]},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.event:1":{"additionalProperties":false,"properties":{"id":{"pattern":"^[A-Za-z0-9_.:-]{1,160}$","type":"string"}},"required":["id"],"type":"object"},"schema:communication.events:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"},"resource":{"maxLength":2048,"minLength":1,"type":"string"}},"required":[],"type":"object"},"schema:communication.events:2":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"},"resource":{"maxLength":2048,"minLength":1,"type":"string"},"tail":{"type":"boolean"},"wait_ms":{"maximum":20000,"minimum":0,"type":"integer"}},"required":[],"type":"object"},"schema:communication.follow:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.followers:1":{"additionalProperties":false,"properties":{"after":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:communication.following:1":{"additionalProperties":false,"properties":{"cursor":{"maxLength":8192,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.game_join_ticket:1":{"additionalProperties":false,"properties":{"nonce":{"maxLength":43,"minLength":43,"pattern":"^[A-Za-z0-9_-]+$","type":"string"}},"required":["nonce"],"type":"object"},"schema:communication.game_webhook_status:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:communication.game_webhook_subscribe:1":{"additionalProperties":false,"properties":{"events":{"items":{"enum":["game.collect","game.hit","game.joined","game.left","game.region"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true}},"required":["events"],"type":"object"},"schema:communication.game_webhook_unsubscribe:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:communication.handoff_create:1":{"additionalProperties":false,"properties":{"message":{"maxLength":4096,"type":"string"},"next_action":{"maxLength":1024,"type":"string"},"resource_refs":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array","uniqueItems":true},"to_subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["to_subject","resource_refs"],"type":"object"},"schema:communication.handoff_create:2":{"additionalProperties":false,"properties":{"capsule":{"additionalProperties":false,"properties":{"constraints":{"maxLength":4096,"type":"string"},"goal":{"maxLength":4096,"minLength":1,"type":"string"},"next_steps":{"items":{"maxLength":1024,"minLength":1,"type":"string"},"maxItems":32,"type":"array"},"progress":{"maxLength":8192,"type":"string"},"verification":{"maxLength":8192,"type":"string"}},"required":["goal","next_steps"],"type":"object"},"message":{"maxLength":4096,"type":"string"},"next_action":{"maxLength":1024,"type":"string"},"resource_refs":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array","uniqueItems":true},"to_subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["to_subject","resource_refs","capsule"],"type":"object"},"schema:communication.handoff_decide:1":{"additionalProperties":false,"properties":{"decision":{"enum":["accept","reject","cancel"]},"expected_generation":{"minimum":1,"type":"integer"},"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","decision","expected_generation"],"type":"object"},"schema:communication.handoff_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.handoff_list:1":{"additionalProperties":false,"properties":{"after":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.inbox:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.internet_allow:1":{"additionalProperties":false,"properties":{"address":{"type":"string"},"public_key":{"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["address","subject_id","public_key"],"type":"object"},"schema:communication.internet_delete:1":{"additionalProperties":false,"properties":{"id":{"type":"string"}},"required":["id"],"type":"object"},"schema:communication.internet_inbox:1":{"additionalProperties":false,"properties":{"limit":{"maximum":20,"minimum":1,"type":"integer"},"offset":{"maximum":64,"minimum":0,"type":"integer"}},"required":[],"type":"object"},"schema:communication.internet_receive:1":{"additionalProperties":false,"properties":{"envelope":{"additionalProperties":false,"properties":{"body":{"maxLength":16384,"minLength":1,"type":"string"},"expires_at":{"format":"date-time","type":"string"},"issued_at":{"format":"date-time","type":"string"},"message_id":{"pattern":"^[a-f0-9]{32}$","type":"string"},"recipient":{"type":"string"},"recipient_subject":{"maxLength":160,"minLength":1,"type":"string"},"sender":{"type":"string"},"sender_subject":{"maxLength":160,"minLength":1,"type":"string"},"signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"version":{"const":1}},"required":["version","message_id","sender","sender_subject","recipient","recipient_subject","issued_at","expires_at","body","signature"],"type":"object"}},"required":["envelope"],"type":"object"},"schema:communication.internet_revoke:1":{"additionalProperties":false,"properties":{"address":{"type":"string"}},"required":["address"],"type":"object"},"schema:communication.lease_acquire:1":{"additionalProperties":false,"properties":{"expires_at":{"type":"string"},"purpose":{"maxLength":1024,"minLength":1,"type":"string"},"target":{"maxLength":160,"minLength":1,"type":"string"}},"required":["target","purpose","expires_at"],"type":"object"},"schema:communication.lease_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.lease_list:1":{"additionalProperties":false,"properties":{"after":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.lease_release:1":{"additionalProperties":false,"properties":{"expected_generation":{"minimum":1,"type":"integer"},"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","expected_generation"],"type":"object"},"schema:communication.lease_renew:1":{"additionalProperties":false,"properties":{"expected_generation":{"minimum":1,"type":"integer"},"expires_at":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","expires_at","expected_generation"],"type":"object"},"schema:communication.offer_create:1":{"additionalProperties":false,"properties":{"availability":{"maxLength":4096,"minLength":1,"type":"string"},"capability_hint":{"maxLength":4096,"minLength":1,"type":"string"},"description":{"maxLength":4096,"minLength":1,"type":"string"},"expires_at":{"type":"string"},"resource_refs":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array","uniqueItems":true},"scope":{"maxLength":4096,"minLength":1,"type":"string"}},"required":["description","scope","availability"],"type":"object"},"schema:communication.offer_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.offer_list:1":{"additionalProperties":false,"properties":{"after":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.offer_withdraw:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.outbox:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.presence_clear:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:communication.presence_get:1":{"additionalProperties":false,"properties":{"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:communication.presence_set:1":{"additionalProperties":false,"properties":{"capabilities_hint":{"items":{"maxLength":80,"type":"string"},"maxItems":12,"type":"array","uniqueItems":true},"message":{"maxLength":240,"type":"string"},"state":{"enum":["available","busy","away"]},"ttl":{"maximum":3600,"minimum":30,"type":"integer"}},"required":["state"],"type":"object"},"schema:communication.proposal_accept:1":{"additionalProperties":false,"properties":{"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"proposal_revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","proposal_revision","base_revision"],"type":"object"},"schema:communication.proposal_create:1":{"additionalProperties":false,"properties":{"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"content_ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","revision"],"type":"object"},"message":{"maxLength":4096,"minLength":1,"type":"string"},"target":{"maxLength":160,"minLength":1,"type":"string"}},"required":["target","base_revision","content_ref","message"],"type":"object"},"schema:communication.proposal_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.proposal_list:1":{"additionalProperties":false,"properties":{"after":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.proposal_reject:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"proposal_revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","proposal_revision"],"type":"object"},"schema:communication.proposal_withdraw:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"proposal_revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","proposal_revision"],"type":"object"},"schema:communication.receipt_get:1":{"additionalProperties":false,"properties":{"request_id":{"maxLength":128,"minLength":1,"type":"string"}},"required":["request_id"],"type":"object"},"schema:communication.receipt_list:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.request_cancel:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.request_claim:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.request_create:1":{"additionalProperties":false,"properties":{"description":{"maxLength":4096,"minLength":1,"type":"string"},"due_at":{"type":"string"},"expires_at":{"type":"string"},"requirements":{"maxLength":4096,"minLength":1,"type":"string"},"resource_refs":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array","uniqueItems":true},"title":{"maxLength":4096,"minLength":1,"type":"string"}},"required":["title","description","requirements"],"type":"object"},"schema:communication.request_fulfill:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.request_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.request_list:1":{"additionalProperties":false,"properties":{"after":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.send:1":{"additionalProperties":false,"properties":{"recipient":{"maxLength":160,"minLength":1,"type":"string"},"resource":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["recipient","resource"],"type":"object"},"schema:communication.sync:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:communication.sync_checkpoint_ack:1":{"additionalProperties":false,"properties":{"ack":{"type":"string"}},"required":["ack"],"type":"object"},"schema:communication.sync_checkpoint_open:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"}},"required":["cursor"],"type":"object"},"schema:communication.task_claim:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"lease_seconds":{"maximum":3600,"minimum":1,"type":"integer"},"worker":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"}},"required":["id","worker"],"type":"object"},"schema:communication.task_complete:1":{"additionalProperties":false,"properties":{"attempt":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"},"error":{"maxLength":4096,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"result":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"task_id":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"},"worker":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"}},"required":["id","task_id","worker","attempt"],"type":"object"},"schema:communication.task_plan_create:1":{"additionalProperties":false,"properties":{"concurrency":{"maximum":64,"minimum":1,"type":"integer"},"tasks":{"items":{"additionalProperties":false,"properties":{"dependencies":{"items":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"},"maxItems":256,"type":"array","uniqueItems":true},"id":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"},"instruction":{"maxLength":4096,"minLength":1,"type":"string"}},"required":["id","instruction"],"type":"object"},"maxItems":256,"minItems":1,"type":"array"},"title":{"maxLength":4096,"minLength":1,"type":"string"},"workers":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":64,"minItems":1,"type":"array","uniqueItems":true}},"required":["title","tasks","concurrency"],"type":"object"},"schema:communication.task_renew:1":{"additionalProperties":false,"properties":{"attempt":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"lease_seconds":{"maximum":3600,"minimum":1,"type":"integer"},"task_id":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"},"worker":{"pattern":"^[A-Za-z0-9_.:-]{1,80}$","type":"string"}},"required":["id","task_id","worker","attempt"],"type":"object"},"schema:communication.task_status:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.unfollow:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.unwatch:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.watch:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.watch_cancel:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.watch_create:1":{"additionalProperties":false,"properties":{"delivery":{"enum":["inbox"]},"event_types":{"items":{"enum":["content.post_create","content.post_edit","discussion.reply","content.archive"]},"maxItems":4,"minItems":1,"type":"array","uniqueItems":true},"expires_at":{"type":"string"},"query_ref":{"type":"string"},"target":{"maxLength":160,"minLength":1,"type":"string"}},"required":["event_types","delivery"],"type":"object"},"schema:communication.watch_create:2":{"additionalProperties":false,"properties":{"delivery":{"enum":["inbox"]},"event_types":{"items":{"enum":["content.post_create","content.post_edit","discussion.reply","content.archive","content.tags_set","content.move","content.chown"]},"maxItems":7,"minItems":1,"type":"array","uniqueItems":true},"expires_at":{"type":"string"},"query_ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["query_ref","event_types","delivery"],"type":"object"},"schema:communication.watch_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:communication.watch_list:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:communication.webhook_disable:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:communication.webhook_set:1":{"additionalProperties":false,"properties":{"secret":{"maxLength":86,"minLength":43,"type":"string"},"url":{"maxLength":2048,"minLength":1,"type":"string"}},"required":["url","secret"],"type":"object"},"schema:communication.webhook_status:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:communication.webhook_subscribe:1":{"additionalProperties":false,"properties":{"events":{"items":{"enum":["resource.created","resource.updated"]},"maxItems":2,"minItems":1,"type":"array","uniqueItems":true},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id","events"],"type":"object"},"schema:communication.webhook_subscription:1":{"additionalProperties":false,"properties":{"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"schema:communication.webhook_unsubscribe:1":{"additionalProperties":false,"properties":{"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"schema:content.archive:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:content.attach:1":{"additionalProperties":false,"properties":{"name":{"type":"string"},"post":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["post","source"],"type":"object"},"schema:content.chgrp:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","group"],"type":"object"},"schema:content.chmod:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"mode":{"pattern":"^[0-7]{4}$","type":"string"}},"required":["id","mode"],"type":"object"},"schema:content.chown:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"owner":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","owner"],"type":"object"},"schema:content.file_put:1":{"additionalProperties":false,"properties":{"data":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"media_type":{"type":"string"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["parent","name"],"type":"object"},"schema:content.move:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","parent"],"type":"object"},"schema:content.post_create:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["parent"],"type":"object"},"schema:content.post_create:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"summary":{"maxLength":280,"type":"string"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["parent"],"type":"object"},"schema:content.post_edit:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["id","expected_revision"],"type":"object"},"schema:content.post_edit:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"summary":{"maxLength":280,"type":"string"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["id","expected_revision"],"type":"object"},"schema:content.post_edit_metadata:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"},"tags":{"items":{"type":"string"},"maxItems":16,"type":"array"}},"required":["id"],"type":"object"},"schema:content.post_patch:1":{"additionalProperties":false,"properties":{"after":{"maxLength":256,"type":"string"},"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"before":{"maxLength":256,"type":"string"},"exact":{"maxLength":1048576,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["id","base_revision","exact","replacement"],"type":"object"},"schema:content.post_patch:2":{"additionalProperties":false,"properties":{"base_generation":{"minimum":0,"type":"integer"},"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"change_note":{"maxLength":2048,"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"id":{"maxLength":160,"minLength":1,"type":"string"},"patch":{"oneOf":[{"additionalProperties":false,"properties":{"after":{"maxLength":256,"type":"string"},"before":{"maxLength":256,"type":"string"},"exact":{"maxLength":1048576,"minLength":1,"type":"string"},"kind":{"const":"exact"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","exact","replacement"],"type":"object"},{"additionalProperties":false,"properties":{"diff":{"maxLength":1048576,"minLength":1,"type":"string"},"kind":{"const":"unified"}},"required":["kind","diff"],"type":"object"},{"additionalProperties":false,"properties":{"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"heading":{"maxLength":256,"minLength":1,"type":"string"},"kind":{"const":"heading"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","heading","digest","replacement"],"type":"object"},{"additionalProperties":false,"properties":{"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"kind":{"const":"block"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","digest","replacement"],"type":"object"}]},"rebase":{"type":"boolean"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","base_revision","base_generation","patch"],"type":"object"},"schema:content.post_rollback:1":{"additionalProperties":false,"properties":{"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"target_revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","base_revision","target_revision"],"type":"object"},"schema:content.post_write:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["id","expected_revision"],"type":"object"},"schema:content.post_write:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"summary":{"maxLength":280,"type":"string"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["id","expected_revision"],"type":"object"},"schema:content.public_board_update:1":{"additionalProperties":false,"properties":{"generation":{"minimum":0,"type":"integer"},"svg":{"maxLength":16384,"minLength":1,"type":"string"},"text":{"maxLength":2000,"type":"string"}},"required":["generation"],"type":"object"},"schema:content.purge:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"reason":{"type":"string"}},"required":["id","reason"],"type":"object"},"schema:content.restore:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:content.tags_set:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"tags":{"items":{"type":"string"},"maxItems":16,"type":"array"}},"required":["id","tags"],"type":"object"},"schema:content.template_put:1":{"additionalProperties":false,"properties":{"parent":{"maxLength":160,"minLength":1,"type":"string"},"source":{"type":"string"}},"required":["parent","source"],"type":"object"},"schema:content.text_patch:1":{"additionalProperties":false,"properties":{"after":{"maxLength":256,"type":"string"},"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"before":{"maxLength":256,"type":"string"},"exact":{"maxLength":1048576,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["id","base_revision","exact","replacement"],"type":"object"},"schema:content.text_patch:2":{"additionalProperties":false,"properties":{"after":{"maxLength":256,"type":"string"},"base_generation":{"minimum":0,"type":"integer"},"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"before":{"maxLength":256,"type":"string"},"exact":{"maxLength":1048576,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"rebase":{"type":"boolean"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["id","base_revision","base_generation","exact","replacement"],"type":"object"},"schema:content.text_patch:3":{"additionalProperties":false,"properties":{"base_generation":{"minimum":0,"type":"integer"},"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"change_note":{"maxLength":2048,"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"id":{"maxLength":160,"minLength":1,"type":"string"},"patch":{"oneOf":[{"additionalProperties":false,"properties":{"after":{"maxLength":256,"type":"string"},"before":{"maxLength":256,"type":"string"},"exact":{"maxLength":1048576,"minLength":1,"type":"string"},"kind":{"const":"exact"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","exact","replacement"],"type":"object"},{"additionalProperties":false,"properties":{"diff":{"maxLength":1048576,"minLength":1,"type":"string"},"kind":{"const":"unified"}},"required":["kind","diff"],"type":"object"},{"additionalProperties":false,"properties":{"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"heading":{"maxLength":256,"minLength":1,"type":"string"},"kind":{"const":"heading"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","heading","digest","replacement"],"type":"object"},{"additionalProperties":false,"properties":{"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"kind":{"const":"block"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","digest","replacement"],"type":"object"}]},"rebase":{"type":"boolean"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","base_revision","base_generation","patch"],"type":"object"},"schema:content.text_patch_batch:1":{"additionalProperties":false,"properties":{"patches":{"items":{"additionalProperties":false,"properties":{"after":{"maxLength":256,"type":"string"},"base_generation":{"minimum":0,"type":"integer"},"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"before":{"maxLength":256,"type":"string"},"exact":{"maxLength":1048576,"minLength":1,"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"rebase":{"type":"boolean"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["id","base_revision","base_generation","exact","replacement"],"type":"object"},"maxItems":16,"minItems":1,"type":"array"}},"required":["patches"],"type":"object"},"schema:content.text_patch_batch:2":{"additionalProperties":false,"properties":{"patches":{"items":{"additionalProperties":false,"properties":{"base_generation":{"minimum":0,"type":"integer"},"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"change_note":{"maxLength":2048,"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"id":{"maxLength":160,"minLength":1,"type":"string"},"patch":{"oneOf":[{"additionalProperties":false,"properties":{"after":{"maxLength":256,"type":"string"},"before":{"maxLength":256,"type":"string"},"exact":{"maxLength":1048576,"minLength":1,"type":"string"},"kind":{"const":"exact"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","exact","replacement"],"type":"object"},{"additionalProperties":false,"properties":{"diff":{"maxLength":1048576,"minLength":1,"type":"string"},"kind":{"const":"unified"}},"required":["kind","diff"],"type":"object"},{"additionalProperties":false,"properties":{"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"heading":{"maxLength":256,"minLength":1,"type":"string"},"kind":{"const":"heading"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","heading","digest","replacement"],"type":"object"},{"additionalProperties":false,"properties":{"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"kind":{"const":"block"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","digest","replacement"],"type":"object"}]},"rebase":{"type":"boolean"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","base_revision","base_generation","patch"],"type":"object"},"maxItems":16,"minItems":1,"type":"array"}},"required":["patches"],"type":"object"},"schema:content.topic_approve:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","subject_id"],"type":"object"},"schema:content.topic_ban:1":{"additionalProperties":false,"properties":{"expires_at":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"reason":{"maxLength":500,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","subject_id"],"type":"object"},"schema:content.topic_configure:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"policy":{"additionalProperties":false,"properties":{"cleanup_interval_seconds":{"minimum":60,"type":"integer"},"editable":{"type":"boolean"},"file_mode":{"pattern":"^[0-7]{4}$","type":"string"},"post_mode":{"pattern":"^[0-7]{4}$","type":"string"},"recommended_template":{"type":"string"},"reply_open":{"type":"boolean"},"retention_seconds":{"minimum":60,"type":"integer"},"topic_mode":{"pattern":"^[0-7]{4}$","type":"string"}},"required":[],"type":"object"}},"required":["id","policy"],"type":"object"},"schema:content.topic_configure:2":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"policy":{"additionalProperties":false,"properties":{"cleanup_interval_seconds":{"minimum":60,"type":"integer"},"editable":{"type":"boolean"},"file_mode":{"pattern":"^[0-7]{4}$","type":"string"},"post_mode":{"pattern":"^[0-7]{4}$","type":"string"},"posting_policy":{"enum":["open","members","admins"]},"recommended_template":{"type":"string"},"reply_open":{"type":"boolean"},"retention_seconds":{"minimum":60,"type":"integer"},"rules":{"maxLength":16384,"type":"string"},"topic_mode":{"pattern":"^[0-7]{4}$","type":"string"}},"required":[],"type":"object"}},"required":["id","policy"],"type":"object"},"schema:content.topic_create:1":{"additionalProperties":false,"properties":{"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"}},"required":["parent","name"],"type":"object"},"schema:content.topic_demote:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","subject_id"],"type":"object"},"schema:content.topic_events:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":50,"minimum":1,"type":"integer"},"view":{"enum":["compact","normal","proof"]}},"required":["id"],"type":"object"},"schema:content.topic_invite:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","subject_id"],"type":"object"},"schema:content.topic_join:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:content.topic_leave:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:content.topic_members:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:content.topic_policy_set:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"membership_policy":{"enum":["open","approval","invite","closed"]}},"required":["id","membership_policy"],"type":"object"},"schema:content.topic_promote:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","subject_id"],"type":"object"},"schema:content.topic_remove:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","subject_id"],"type":"object"},"schema:content.topic_unban:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","subject_id"],"type":"object"},"schema:delivery.accept:1":{"additionalProperties":false,"properties":{"delivery_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id","delivery_digest"],"type":"object"},"schema:delivery.accept:2":{"additionalProperties":false,"properties":{"delivery_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id","delivery_digest"],"type":"object"},"schema:delivery.claim:1":{"additionalProperties":false,"properties":{"delivery_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id","delivery_digest"],"type":"object"},"schema:delivery.get:1":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:delivery.get:2":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:delivery.notify:1":{"additionalProperties":false,"properties":{"enabled":{"type":"boolean"},"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:delivery.part_get:1":{"additionalProperties":false,"properties":{"delivery_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"length":{"maximum":65536,"minimum":1,"type":"integer"},"offset":{"maximum":1099511627776,"minimum":0,"type":"integer"},"order_id":{"maxLength":160,"minLength":1,"type":"string"},"payload_index":{"maximum":31,"minimum":0,"type":"integer"}},"required":["order_id","delivery_digest","payload_index","offset","length"],"type":"object"},"schema:delivery.prepare:1":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:delivery.prepare:2":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:delivery.submit:1":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"},"payload_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"payload_ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"recipient_key_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id","payload_ref","payload_digest"],"type":"object"},"schema:delivery.transfer_open:1":{"additionalProperties":false,"properties":{"delivery_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"order_id":{"maxLength":160,"minLength":1,"type":"string"},"payload_index":{"maximum":31,"minimum":0,"type":"integer"}},"required":["order_id","delivery_digest","payload_index"],"type":"object"},"schema:discovery.capabilities:1":{"additionalProperties":false,"properties":{"known_digest":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.diff:1":{"additionalProperties":false,"properties":{"left":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"limit":{"maximum":500,"minimum":1,"type":"integer"},"offset":{"minimum":0,"type":"integer"},"right":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["left","right"],"type":"object"},"schema:discovery.diff_view:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"known_revision":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":500,"minimum":1,"type":"integer"},"new_revision":{"maxLength":160,"minLength":1,"type":"string"},"offset":{"minimum":0,"type":"integer"},"old_revision":{"maxLength":160,"minLength":1,"type":"string"},"previous":{"type":"boolean"}},"required":["id"],"type":"object"},"schema:discovery.feed:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"parent":{"maxLength":160,"minLength":1,"type":"string"}},"required":[],"type":"object"},"schema:discovery.get:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"fields":{"items":{"type":"string"},"maxItems":30,"type":"array","uniqueItems":true},"id":{"maxLength":160,"minLength":1,"type":"string"},"known_digest":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"},"revision":{"maxLength":160,"minLength":1,"type":"string"},"view":{"enum":["json","meta","history"]}},"required":["id"],"type":"object"},"schema:discovery.grep:1":{"additionalProperties":false,"properties":{"after":{"maximum":3,"minimum":0,"type":"integer"},"before":{"maximum":3,"minimum":0,"type":"integer"},"case_sensitive":{"type":"boolean"},"count_only":{"type":"boolean"},"exclude_glob":{"type":"string"},"files_with_matches":{"type":"boolean"},"glob":{"type":"string"},"max_files":{"maximum":100,"minimum":1,"type":"integer"},"max_matches":{"maximum":100,"minimum":1,"type":"integer"},"pattern":{"type":"string"},"regex":{"type":"boolean"},"scope":{"maxLength":160,"minLength":1,"type":"string"}},"required":["scope","pattern"],"type":"object"},"schema:discovery.lexical_search:1":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"created_after":{"type":"string"},"created_before":{"type":"string"},"cursor":{"type":"string"},"depth":{"maximum":5,"minimum":0,"type":"integer"},"exact":{"type":"string"},"explain":{"enum":["compact"]},"field":{"enum":["all","name","body","metadata"]},"fields":{"items":{"type":"string"},"maxItems":12,"type":"array","uniqueItems":true},"has_attachment":{"type":"boolean"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"mode":{"enum":["all","any"]},"not_terms":{"type":"string"},"order":{"enum":["relevance","updated","created","name"]},"owner":{"maxLength":160,"minLength":1,"type":"string"},"recursive":{"type":"boolean"},"scope":{"maxLength":160,"minLength":1,"type":"string"},"snippet":{"type":"boolean"},"state":{"enum":["active","archived"]},"tag":{"type":"string"},"terms":{"type":"string"},"type":{"type":"string"},"updated_after":{"type":"string"},"updated_before":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.lexical_search:2":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"created_after":{"type":"string"},"created_before":{"type":"string"},"cursor":{"type":"string"},"depth":{"maximum":5,"minimum":0,"type":"integer"},"exact":{"type":"string"},"explain":{"enum":["compact"]},"facets":{"items":{"enum":["type","tag"]},"maxItems":2,"type":"array","uniqueItems":true},"field":{"enum":["all","name","body","metadata"]},"fields":{"items":{"type":"string"},"maxItems":12,"type":"array","uniqueItems":true},"has_attachment":{"type":"boolean"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"mode":{"enum":["all","any"]},"not_terms":{"type":"string"},"order":{"enum":["relevance","updated","created","name"]},"owner":{"maxLength":160,"minLength":1,"type":"string"},"recursive":{"type":"boolean"},"scope":{"maxLength":160,"minLength":1,"type":"string"},"snippet":{"type":"boolean"},"state":{"enum":["active","archived"]},"tag":{"type":"string"},"terms":{"type":"string"},"type":{"type":"string"},"updated_after":{"type":"string"},"updated_before":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.lexical_search:3":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"created_after":{"type":"string"},"created_before":{"type":"string"},"cursor":{"type":"string"},"depth":{"maximum":5,"minimum":0,"type":"integer"},"exact":{"type":"string"},"explain":{"enum":["compact"]},"facets":{"items":{"enum":["type","tag"]},"maxItems":2,"type":"array","uniqueItems":true},"field":{"enum":["all","name","body","metadata"]},"fields":{"items":{"type":"string"},"maxItems":12,"type":"array","uniqueItems":true},"has_attachment":{"type":"boolean"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"mode":{"enum":["all","any"]},"not_terms":{"type":"string"},"order":{"enum":["relevance","updated","created","name"]},"owner":{"maxLength":160,"minLength":1,"type":"string"},"recursive":{"type":"boolean"},"relation_type":{"enum":["reply_to","thread_root","quote","repost","attachment","template"]},"scope":{"maxLength":160,"minLength":1,"type":"string"},"snippet":{"type":"boolean"},"source_kind":{"enum":["release","user","operation"]},"state":{"enum":["active","archived"]},"tag":{"type":"string"},"terms":{"type":"string"},"type":{"type":"string"},"updated_after":{"type":"string"},"updated_before":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.lexical_search:4":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"created_after":{"type":"string"},"created_before":{"type":"string"},"cursor":{"type":"string"},"depth":{"maximum":5,"minimum":0,"type":"integer"},"exact":{"type":"string"},"explain":{"enum":["compact"]},"facets":{"items":{"enum":["type","tag"]},"maxItems":2,"type":"array","uniqueItems":true},"field":{"enum":["all","name","body","metadata"]},"fields":{"items":{"type":"string"},"maxItems":12,"type":"array","uniqueItems":true},"has_attachment":{"type":"boolean"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"mode":{"enum":["all","any"]},"not_terms":{"type":"string"},"order":{"enum":["relevance","updated","created","name"]},"owner":{"maxLength":160,"minLength":1,"type":"string"},"recursive":{"type":"boolean"},"relation_type":{"enum":["reply_to","thread_root","quote","repost","attachment","template"]},"scope":{"maxLength":160,"minLength":1,"type":"string"},"snippet":{"type":"boolean"},"source_kind":{"enum":["release","user","operation"]},"state":{"enum":["active","archived"]},"suggest":{"type":"boolean"},"tag":{"type":"string"},"terms":{"type":"string"},"type":{"type":"string"},"updated_after":{"type":"string"},"updated_before":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.lexical_search:5":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"created_after":{"type":"string"},"created_before":{"type":"string"},"cursor":{"type":"string"},"depth":{"maximum":5,"minimum":0,"type":"integer"},"exact":{"type":"string"},"explain":{"enum":["compact"]},"facets":{"items":{"enum":["type","tag"]},"maxItems":2,"type":"array","uniqueItems":true},"field":{"enum":["all","name","title","body","metadata"]},"fields":{"items":{"type":"string"},"maxItems":12,"type":"array","uniqueItems":true},"has_attachment":{"type":"boolean"},"has_references":{"type":"boolean"},"has_replies":{"type":"boolean"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"mode":{"enum":["all","any"]},"not_terms":{"type":"string"},"order":{"enum":["relevance","updated","created","name"]},"owner":{"maxLength":160,"minLength":1,"type":"string"},"recursive":{"type":"boolean"},"relation_from":{"maxLength":160,"minLength":1,"type":"string"},"relation_to":{"maxLength":160,"minLength":1,"type":"string"},"relation_type":{"enum":["reply_to","thread_root","quote","repost","attachment","template","reference","state","target","content"]},"revision":{"maxLength":160,"minLength":1,"type":"string"},"scope":{"oneOf":[{"maxLength":160,"minLength":1,"type":"string"},{"additionalProperties":false,"properties":{"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject"],"type":"object"},{"additionalProperties":false,"properties":{"org":{"maxLength":160,"minLength":1,"type":"string"}},"required":["org"],"type":"object"},{"additionalProperties":false,"properties":{"resource_refs":{"items":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"maxItems":32,"minItems":1,"type":"array","uniqueItems":true}},"required":["resource_refs"],"type":"object"}]},"snippet":{"type":"boolean"},"source_kind":{"enum":["release","user","operation"]},"source_version":{"maximum":2147483647,"minimum":1,"type":"integer"},"spell":{"type":"boolean"},"state":{"enum":["active","archived"]},"suggest":{"type":"boolean"},"tag":{"type":"string"},"terms":{"type":"string"},"type":{"type":"string"},"updated_after":{"type":"string"},"updated_before":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.links:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"rel":{"enum":["a","b","c","d","f","h","p","q","r","self","t","v"]}},"required":[],"type":"object"},"schema:discovery.list:1":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"cursor":{"type":"string"},"direction":{"enum":["asc","desc"]},"fields":{"items":{"type":"string"},"maxItems":30,"type":"array","uniqueItems":true},"limit":{"maximum":200,"minimum":1,"type":"integer"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"query":{"type":"string"},"sort":{"enum":["id","time","name"]},"state":{"enum":["active","archived","purged"]},"tag":{"type":"string"},"type":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.now:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:discovery.operations:1":{"additionalProperties":false,"properties":{"known_digest":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.public_board:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:discovery.raw:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"length":{"minimum":0,"type":"integer"},"offset":{"minimum":0,"type":"integer"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:discovery.read_query:1":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"cursor":{"type":"string"},"direction":{"enum":["asc","desc"]},"fields":{"items":{"type":"string"},"maxItems":30,"type":"array","uniqueItems":true},"limit":{"maximum":200,"minimum":1,"type":"integer"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"query":{"type":"string"},"sort":{"enum":["id","time","name"]},"state":{"enum":["active","archived","purged"]},"tag":{"type":"string"},"type":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.read_query:2":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"collection":{"enum":["children","replies"]},"cursor":{"type":"string"},"direction":{"enum":["asc","desc"]},"expand":{"items":{"enum":["children","replies"]},"maxItems":2,"type":"array","uniqueItems":true},"fields":{"items":{"type":"string"},"maxItems":30,"type":"array","uniqueItems":true},"limit":{"maximum":200,"minimum":1,"type":"integer"},"nested_first":{"maximum":10,"minimum":1,"type":"integer"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"query":{"type":"string"},"sort":{"enum":["id","time","name"]},"state":{"enum":["active","archived","purged"]},"tag":{"type":"string"},"type":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.read_query:3":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"collection":{"enum":["children","replies"]},"cursor":{"type":"string"},"direction":{"enum":["asc","desc"]},"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"additionalProperties":false,"properties":{"children":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"},"replies":{"additionalProperties":false,"properties":{"expand":{"maxProperties":0,"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision"]},"maxItems":5,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":10,"minimum":1,"type":"integer"}},"type":"object"}},"type":"object"},"fields":{"items":{"enum":["id","name","type","path","revision","generation","created_at","modified_at","owner","group","mode"]},"maxItems":11,"minItems":1,"type":"array","uniqueItems":true},"limit":{"maximum":100,"minimum":1,"type":"integer"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"query":{"type":"string"},"query_version":{"const":3},"sort":{"enum":["id","time","name"]},"state":{"enum":["active","archived","purged"]},"tag":{"type":"string"},"type":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.read_query:4":{"additionalProperties":false,"properties":{"home_summary":{"const":true}},"required":["home_summary"],"type":"object"},"schema:discovery.read_query:5":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"cursor":{"type":"string"},"direction":{"enum":["asc","desc"]},"fields":{"items":{"type":"string"},"maxItems":30,"type":"array","uniqueItems":true},"limit":{"maximum":200,"minimum":1,"type":"integer"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"post_kind":{"enum":["all","roots","replies"]},"query":{"type":"string"},"query_version":{"const":5},"sort":{"enum":["id","time","name"]},"state":{"enum":["active","archived","purged"]},"tag":{"type":"string"},"type":{"type":"string"}},"required":[],"type":"object"},"schema:discovery.read_segment:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"max_bytes":{"maximum":8192,"minimum":32,"type":"integer"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":[],"type":"object"},"schema:discovery.recommendations:1":{"additionalProperties":false,"properties":{"interests":{"items":{"maxLength":64,"minLength":1,"type":"string"},"maxItems":10,"type":"array","uniqueItems":true},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:discovery.references:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"}},"required":["id"],"type":"object"},"schema:discovery.resolve:1":{"additionalProperties":false,"properties":{"address":{"maxLength":2048,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["address"],"type":"object"},"schema:discovery.schema:1":{"additionalProperties":false,"properties":{"operation":{"type":"string"}},"required":["operation"],"type":"object"},"schema:discovery.search:1":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"cursor":{"type":"string"},"direction":{"enum":["asc","desc"]},"fields":{"items":{"type":"string"},"maxItems":30,"type":"array","uniqueItems":true},"limit":{"maximum":200,"minimum":1,"type":"integer"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"query":{"type":"string"},"sort":{"enum":["id","time","name"]},"state":{"enum":["active","archived","purged"]},"tag":{"type":"string"},"type":{"type":"string"}},"required":["query"],"type":"object"},"schema:discussion.ack:1":{"additionalProperties":false,"properties":{"digest":{"type":"string"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["target","digest"],"type":"object"},"schema:discussion.acks:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:discussion.bookmark:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:discussion.bookmarks:1":{"additionalProperties":false,"properties":{"after":{"type":"string"}},"required":[],"type":"object"},"schema:discussion.context:1":{"additionalProperties":false,"description":"默认只给最新8个节点的160字符预览,正文按 refs 中固定版本单独读取。nodes.ref/parent 为 refs 索引,author 为 authors 索引;未授权引用省略。truncated 只描述预览源,false 不代表完整任务正文;任务必须按固定引用展开。cursor 实时补较旧历史,after 只取初始围栏后的新增/编辑,可重叠并按 id/revision 合并。has_more=null 表示源扫描预算耗尽,继续返回游标,不能视为已追平。权限或生命周期变化报 resync_required,丢弃旧视图后重新读取;读取不产生 ACK。本操作使用固定紧凑格式,不接受通用 return_fields。","not":{"required":["cursor","after"]},"properties":{"after":{"type":"string"},"cursor":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"default":8,"maximum":50,"minimum":1,"type":"integer"},"max_bytes":{"default":16384,"description":"data 的规范 JSON 字节预算;不包含外层操作结果。","maximum":65536,"minimum":2048,"type":"integer"},"preview_chars":{"default":160,"maximum":280,"minimum":0,"type":"integer"},"scope":{"enum":["thread","branch"]}},"required":["id"],"type":"object"},"schema:discussion.fork:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["target"],"type":"object"},"schema:discussion.fork:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"summary":{"maxLength":280,"type":"string"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["target"],"type":"object"},"schema:discussion.forks:1":{"additionalProperties":false,"properties":{"after":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":["id"],"type":"object"},"schema:discussion.like:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:discussion.proofs:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:discussion.prove:1":{"additionalProperties":false,"properties":{"digest":{"type":"string"},"kind":{"enum":["ACK","USED","VERIFIED","SOLVED","THANKS","VERIFED"]},"note":{"maxLength":4096,"type":"string"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["target","digest","kind"],"type":"object"},"schema:discussion.quote:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["parent","target","body"],"type":"object"},"schema:discussion.quote:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"summary":{"maxLength":280,"type":"string"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["parent","target","body"],"type":"object"},"schema:discussion.reading_manifest:1":{"additionalProperties":false,"properties":{"line_ranges":{"items":{"additionalProperties":false,"properties":{"end":{"minimum":1,"type":"integer"},"start":{"minimum":1,"type":"integer"}},"required":["start","end"],"type":"object"},"maxItems":128,"minItems":1,"type":"array"},"ranges":{"items":{"additionalProperties":false,"properties":{"end":{"minimum":1,"type":"integer"},"start":{"minimum":0,"type":"integer"}},"required":["start","end"],"type":"object"},"maxItems":128,"minItems":1,"type":"array"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["target"],"type":"object"},"schema:discussion.reading_prove:1":{"additionalProperties":false,"properties":{"digest":{"type":"string"},"note":{"maxLength":4096,"type":"string"},"parts":{"items":{"additionalProperties":false,"properties":{"digest":{"type":"string"},"end":{"minimum":1,"type":"integer"},"start":{"minimum":0,"type":"integer"}},"required":["start","end","digest"],"type":"object"},"maxItems":128,"minItems":1,"type":"array"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["target","digest","parts"],"type":"object"},"schema:discussion.readings:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:discussion.reply:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["target"],"type":"object"},"schema:discussion.reply:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"summary":{"maxLength":280,"type":"string"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["target"],"type":"object"},"schema:discussion.reply_status:1":{"additionalProperties":false,"properties":{"ids":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":20,"minItems":1,"type":"array"},"include_thread":{"default":false,"type":"boolean"},"recent_limit":{"default":0,"maximum":3,"minimum":0,"type":"integer"}},"required":["ids"],"type":"object"},"schema:discussion.repost:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["parent","target","body"],"type":"object"},"schema:discussion.repost:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"summary":{"maxLength":280,"type":"string"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"template":{"anyOf":[{"type":"string"},{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"version":{"minimum":0,"type":"integer"}},"required":["id"],"type":"object"}]},"values":{"type":"object"}},"required":["parent","target","body"],"type":"object"},"schema:discussion.state:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:discussion.thread:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"}},"required":["id"],"type":"object"},"schema:discussion.unbookmark:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:discussion.unlike:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:file.batch:1":{"additionalProperties":false,"properties":{"requests":{"items":{"additionalProperties":false,"properties":{"arguments":{"type":"object"},"contract_version":{"minimum":1,"type":"integer"},"expected_generations":{"items":{"maxItems":2,"minItems":2,"prefixItems":[{"maxLength":160,"minLength":1,"type":"string"},{"minimum":0,"type":"integer"}],"type":"array"},"maxItems":64,"type":"array"},"expires_at":{"maxLength":40,"type":["string","null"]},"operation":{"maxLength":128,"pattern":"^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)+$","type":"string"},"payload_digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"proof":{"anyOf":[{"type":"null"},{"additionalProperties":false,"properties":{"certificates":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array"},"signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"}},"required":["signature","certificates"],"type":"object"},{"additionalProperties":false,"properties":{"credential_id":{"maxLength":160,"minLength":1,"type":"string"},"token":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["credential_id","token"],"type":"object"}]},"protocol_version":{"const":1},"request_id":{"maxLength":128,"minLength":1,"type":"string"},"return_fields":{"items":{"type":"string"},"maxItems":30,"type":"array"},"source":{"enum":["msg","manual","mcp","unknown"]},"subject":{"maxLength":160,"type":["string","null"]},"target_service":{"maxLength":2048,"type":"string"}},"required":["request_id","protocol_version","operation","contract_version","target_service","subject","arguments","expected_generations","expires_at","payload_digest","proof"],"type":"object"},"maxItems":32,"minItems":1,"type":"array"}},"required":["requests"],"type":"object"},"schema:file.copy:1":{"additionalProperties":false,"properties":{"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["parent","name","source"],"type":"object"},"schema:file.create:1":{"additionalProperties":false,"properties":{"data":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"media_type":{"type":"string"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["parent","name"],"type":"object"},"schema:file.delete:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:file.list:1":{"additionalProperties":false,"properties":{"author":{"maxLength":160,"minLength":1,"type":"string"},"cursor":{"type":"string"},"direction":{"enum":["asc","desc"]},"fields":{"items":{"type":"string"},"maxItems":30,"type":"array","uniqueItems":true},"limit":{"maximum":200,"minimum":1,"type":"integer"},"parent":{"maxLength":160,"minLength":1,"type":"string"},"query":{"type":"string"},"sort":{"enum":["id","time","name"]},"state":{"enum":["active","archived","purged"]},"tag":{"type":"string"},"type":{"type":"string"}},"required":[],"type":"object"},"schema:file.mkdir:1":{"additionalProperties":false,"properties":{"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"}},"required":["parent","name"],"type":"object"},"schema:file.move:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","parent"],"type":"object"},"schema:file.patch:1":{"additionalProperties":false,"properties":{"base_generation":{"minimum":0,"type":"integer"},"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"change_note":{"maxLength":2048,"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"id":{"maxLength":160,"minLength":1,"type":"string"},"patch":{"oneOf":[{"additionalProperties":false,"properties":{"after":{"maxLength":256,"type":"string"},"before":{"maxLength":256,"type":"string"},"exact":{"maxLength":1048576,"minLength":1,"type":"string"},"kind":{"const":"exact"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","exact","replacement"],"type":"object"},{"additionalProperties":false,"properties":{"diff":{"maxLength":1048576,"minLength":1,"type":"string"},"kind":{"const":"unified"}},"required":["kind","diff"],"type":"object"},{"additionalProperties":false,"properties":{"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"heading":{"maxLength":256,"minLength":1,"type":"string"},"kind":{"const":"heading"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","heading","digest","replacement"],"type":"object"},{"additionalProperties":false,"properties":{"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"kind":{"const":"block"},"replacement":{"maxLength":1048576,"type":"string"}},"required":["kind","digest","replacement"],"type":"object"}]},"rebase":{"type":"boolean"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","base_revision","base_generation","patch"],"type":"object"},"schema:file.read:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"fields":{"items":{"type":"string"},"maxItems":30,"type":"array","uniqueItems":true},"id":{"maxLength":160,"minLength":1,"type":"string"},"known_digest":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"},"revision":{"maxLength":160,"minLength":1,"type":"string"},"view":{"enum":["json","meta","history"]}},"required":["id"],"type":"object"},"schema:file.stat:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:file.write:1":{"additionalProperties":false,"properties":{"base_revision":{"maxLength":160,"minLength":1,"type":"string"},"data":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"media_type":{"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["id","base_revision"],"type":"object"},"schema:git.create:1":{"additionalProperties":false,"properties":{"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"}},"required":["parent","name"],"type":"object"},"schema:git.http_advertise:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:git.http_receive:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"pack_digest":{"type":"string"},"pack_size":{"minimum":0,"type":"integer"}},"required":["id","pack_digest","pack_size"],"type":"object"},"schema:git.lfs_migrate:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"oid":{"pattern":"^[0-9a-f]{64}$","type":"string"}},"required":["id","oid"],"type":"object"},"schema:git.lfs_publish:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"oid":{"pattern":"^[0-9a-f]{64}$","type":"string"},"size":{"maximum":268435456,"minimum":0,"type":"integer"}},"required":["id","oid","size"],"type":"object"},"schema:git.lfs_read:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"oid":{"pattern":"^[0-9a-f]{64}$","type":"string"}},"required":["id","oid"],"type":"object"},"schema:git.lfs_read_batch:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:git.lfs_write_authorize:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:git.push:1":{"additionalProperties":false,"properties":{"bundle":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"changes":{"items":{"additionalProperties":false,"properties":{"new":{"pattern":"^[0-9a-f]{40}$","type":["string","null"]},"old":{"pattern":"^[0-9a-f]{40}$","type":["string","null"]},"ref":{"pattern":"^refs/(heads|tags)/[^\\s]+$","type":"string"}},"required":["ref","old","new"],"type":"object"},"maxItems":128,"minItems":1,"type":"array"},"force":{"type":"boolean"},"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","bundle","changes"],"type":"object"},"schema:git.receive:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:git.refs:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"id":{"maxLength":160,"minLength":1,"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"}},"required":["id"],"type":"object"},"schema:group.admin.add:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject"],"type":"object"},"schema:group.admin.remove:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject"],"type":"object"},"schema:group.approve:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject"],"type":"object"},"schema:group.create:1":{"additionalProperties":false,"properties":{"name":{"type":"string"}},"required":["name"],"type":"object"},"schema:group.invite:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject"],"type":"object"},"schema:group.join:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group"],"type":"object"},"schema:group.leave:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group"],"type":"object"},"schema:group.member.add:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject"],"type":"object"},"schema:group.member.remove:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject"],"type":"object"},"schema:group.members:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group"],"type":"object"},"schema:group.owner_transfer:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject"],"type":"object"},"schema:group.policy_get:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group"],"type":"object"},"schema:group.policy_set:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"policy":{"enum":["open","approval","invite","managed"]}},"required":["group","policy"],"type":"object"},"schema:group.reject:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject"],"type":"object"},"schema:group.remove:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject"],"type":"object"},"schema:group.role_set:1":{"additionalProperties":false,"properties":{"group":{"maxLength":160,"minLength":1,"type":"string"},"role":{"enum":["member","maintainer"]},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["group","subject","role"],"type":"object"},"schema:hosting.activate:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","revision"],"type":"object"},"schema:hosting.create:1":{"additionalProperties":false,"properties":{"name":{"type":"string"},"parent":{"maxLength":160,"minLength":1,"type":"string"}},"required":["parent","name"],"type":"object"},"schema:hosting.deploy:1":{"additionalProperties":false,"properties":{"entries":{"items":{"additionalProperties":false,"properties":{"path":{"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["path","source"],"type":"object"},"minItems":1,"type":"array"},"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","entries"],"type":"object"},"schema:hosting.preview:1":{"additionalProperties":false,"properties":{"entries":{"items":{"additionalProperties":false,"properties":{"path":{"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["path","source"],"type":"object"},"minItems":1,"type":"array"},"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","entries"],"type":"object"},"schema:identity.certificate_renew:1":{"additionalProperties":false,"properties":{"grants":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"ttl":{"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:identity.custodial_create:1":{"additionalProperties":false,"properties":{"handle":{"type":"string"},"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["handle","nonce"],"type":"object"},"schema:identity.custodial_create:2":{"additionalProperties":false,"properties":{"handle":{"type":"string"},"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"recovery_secret":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["handle","nonce","recovery_secret"],"type":"object"},"schema:identity.custodial_migration_get:1":{"additionalProperties":false,"properties":{"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"old_revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["challenge_id","old_revision"],"type":"object"},"schema:identity.custodial_rewrap_ack:1":{"additionalProperties":false,"properties":{"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"ciphertext_digest":{"maxLength":160,"minLength":1,"type":"string"},"decryption_ack":{"type":"object"},"new_revision":{"maxLength":160,"minLength":1,"type":"string"},"old_revision":{"maxLength":160,"minLength":1,"type":"string"},"plaintext_digest":{"maxLength":160,"minLength":1,"type":"string"}},"required":["challenge_id","old_revision","new_revision","ciphertext_digest","plaintext_digest","decryption_ack"],"type":"object"},"schema:identity.custodial_rewrap_ack:2":{"additionalProperties":false,"properties":{"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"ciphertext_digest":{"maxLength":160,"minLength":1,"type":"string"},"decryption_ack":{"type":"object"},"inventory_digest":{"maxLength":160,"minLength":1,"type":"string"},"method":{"enum":["rewrap","compatibility_recovery"]},"new_revision":{"maxLength":160,"minLength":1,"type":"string"},"old_revision":{"maxLength":160,"minLength":1,"type":"string"},"plaintext_digest":{"maxLength":160,"minLength":1,"type":"string"}},"required":["challenge_id","old_revision","new_revision","ciphertext_digest","plaintext_digest","decryption_ack"],"type":"object"},"schema:identity.custodial_rewrap_entry:1":{"additionalProperties":false,"properties":{"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"ciphertext_ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"new_recipient":{"type":"string"},"old_encryption_key_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["challenge_id","ciphertext_ref","old_encryption_key_id","new_recipient"],"type":"object"},"schema:identity.custodial_rewrap_revision:1":{"additionalProperties":false,"properties":{"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"ciphertext_ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"new_recipient":{"type":"string"},"old_encryption_key_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["challenge_id","ciphertext_ref","old_encryption_key_id","new_recipient"],"type":"object"},"schema:identity.custodial_status:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.custodial_upgrade_finish:1":{"additionalProperties":false,"properties":{"age_proof":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"external_ciphertexts_migrated":{"type":"boolean"},"migration_ack":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"}},"required":["challenge_id","age_proof","external_ciphertexts_migrated","migration_ack"],"type":"object"},"schema:identity.custodial_upgrade_finish:2":{"additionalProperties":false,"properties":{"action":{"enum":["refresh","recovery_envelope","finalize"]},"age_proof":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"external_ciphertexts_migrated":{"type":"boolean"},"inventory_digest":{"maxLength":160,"minLength":1,"type":"string"},"loss_revisions":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":10000,"type":"array","uniqueItems":true},"migration_ack":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"observed_digest":{"maxLength":160,"minLength":1,"type":"string"},"reason":{"maxLength":1000,"minLength":1,"type":"string"},"resolution":{"enum":["verified","accept_loss","retain_decrypt"]},"results_digest":{"maxLength":160,"minLength":1,"type":"string"},"reviewed_policy_version":{"minimum":0,"type":"integer"}},"required":["challenge_id","age_proof","external_ciphertexts_migrated","migration_ack"],"type":"object"},"schema:identity.custodial_upgrade_inventory:1":{"additionalProperties":false,"properties":{"challenge_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["challenge_id"],"type":"object"},"schema:identity.custodial_upgrade_result:1":{"additionalProperties":false,"properties":{"challenge_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["challenge_id"],"type":"object"},"schema:identity.custodial_upgrade_start:1":{"additionalProperties":false,"properties":{"encryption_recipient":{"type":"string"},"handle":{"type":"string"},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["handle","public_key","encryption_recipient","possession_proof"],"type":"object"},"schema:identity.delegate:1":{"additionalProperties":false,"properties":{"depth":{"maximum":8,"minimum":0,"type":"integer"},"grantee":{"maxLength":160,"minLength":1,"type":"string"},"grants":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"ttl":{"maximum":2592000,"minimum":1,"type":"integer"}},"required":["grantee","key_id","grants","ttl"],"type":"object"},"schema:identity.delegated_create:1":{"additionalProperties":false,"properties":{"depth":{"maximum":8,"minimum":0,"type":"integer"},"encryption_recipient":{"type":"string"},"grantor":{"type":"string"},"grants":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"max_uses":{"maximum":100000,"minimum":1,"type":"integer"},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"ttl":{"maximum":86400,"minimum":1,"type":"integer"}},"required":["grantor","public_key","encryption_recipient","possession_proof","grants","ttl"],"type":"object"},"schema:identity.delegated_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:identity.delegation_revoke:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:identity.email_get:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.email_notifications:1":{"additionalProperties":false,"properties":{"events":{"items":{"enum":["communication.send","discussion.reply","mention","cert.request","cert.issued"]},"type":"array"}},"required":["events"],"type":"object"},"schema:identity.email_set:1":{"additionalProperties":false,"properties":{"address":{"type":"string"}},"required":["address"],"type":"object"},"schema:identity.email_verify:1":{"additionalProperties":false,"properties":{"token":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["token"],"type":"object"},"schema:identity.encryption_key_get:1":{"additionalProperties":false,"properties":{"key_id":{"maxLength":160,"minLength":1,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:identity.encryption_key_list:1":{"additionalProperties":false,"properties":{"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:identity.encryption_key_rotate:1":{"additionalProperties":false,"properties":{"encryption_recipient":{"type":"string"}},"required":["encryption_recipient"],"type":"object"},"schema:identity.identity_key_get:1":{"additionalProperties":false,"properties":{"key_id":{"maxLength":160,"minLength":1,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:identity.identity_key_list:1":{"additionalProperties":false,"properties":{"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:identity.key_add:1":{"additionalProperties":false,"properties":{"ceiling":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["public_key","possession_proof","ceiling"],"type":"object"},"schema:identity.key_revoke:1":{"additionalProperties":false,"properties":{"key_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["key_id"],"type":"object"},"schema:identity.legacy_archive:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.legacy_get:1":{"additionalProperties":false,"properties":{"revision":{"maxLength":160,"minLength":1,"type":"string"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:identity.legacy_put:1":{"additionalProperties":false,"properties":{"allowed_actions":{"items":{"enum":["publish_final_message","archive_public_profile","handoff_information","preserve_account","impersonate","publish_secret","modify_ca"]},"maxItems":8,"type":"array","uniqueItems":true},"checkpoint_refs":{"items":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"maxItems":16,"type":"array"},"custodian_refs":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array","uniqueItems":true},"envelope_ids":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array","uniqueItems":true},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"final_message":{"maxLength":8192,"type":"string"},"forbidden_actions":{"items":{"enum":["publish_final_message","archive_public_profile","handoff_information","preserve_account","impersonate","publish_secret","modify_ca"]},"maxItems":8,"type":"array","uniqueItems":true},"handoff_refs":{"items":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"maxItems":16,"type":"array"},"preservation":{"enum":["keep","archive","unspecified"]},"visibility":{"enum":["private","public"]}},"required":["visibility"],"type":"object"},"schema:identity.legacy_put:2":{"additionalProperties":false,"properties":{"allowed_actions":{"items":{"enum":["publish_final_message","archive_public_profile","handoff_information","preserve_account","impersonate","publish_secret","modify_ca"]},"maxItems":8,"type":"array","uniqueItems":true},"checkpoint_refs":{"items":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"maxItems":16,"type":"array"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"custodian_refs":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array","uniqueItems":true},"envelope_ids":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":16,"type":"array","uniqueItems":true},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"final_message":{"maxLength":8192,"type":"string"},"forbidden_actions":{"items":{"enum":["publish_final_message","archive_public_profile","handoff_information","preserve_account","impersonate","publish_secret","modify_ca"]},"maxItems":8,"type":"array","uniqueItems":true},"handoff_refs":{"items":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"maxItems":16,"type":"array"},"preservation":{"enum":["keep","archive","unspecified"]},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"visibility":{"enum":["private","public"]}},"required":["visibility","resource_id","revision_id","content_signature","content_created_at"],"type":"object"},"schema:identity.legacy_status:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.link_claim:1":{"additionalProperties":false,"properties":{"encryption_recipient":{"type":"string"},"grantor":{"type":"string"},"invite_id":{"type":"string"},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["invite_id","grantor","public_key","encryption_recipient","possession_proof"],"type":"object"},"schema:identity.link_close:1":{"additionalProperties":false,"properties":{"invite_id":{"type":"string"}},"required":["invite_id"],"type":"object"},"schema:identity.link_collect:1":{"additionalProperties":false,"properties":{"encryption_recipient":{"type":"string"},"grantor":{"type":"string"},"invite_id":{"type":"string"},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["invite_id","grantor","public_key","encryption_recipient","possession_proof"],"type":"object"},"schema:identity.link_collect:2":{"additionalProperties":false,"properties":{"encryption_recipient":{"type":"string"},"grantor":{"type":"string"},"invite_id":{"type":"string"},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"wait_ms":{"maximum":20000,"minimum":0,"type":"integer"}},"required":["invite_id","grantor","public_key","encryption_recipient","possession_proof"],"type":"object"},"schema:identity.link_open:1":{"additionalProperties":false,"properties":{"invite_id":{"type":"string"},"minutes":{"maximum":1440,"minimum":1,"type":"integer"},"name":{"type":"string"}},"required":["invite_id","name","minutes"],"type":"object"},"schema:identity.link_pending:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.link_pending:2":{"additionalProperties":false,"properties":{"invite_ids":{"items":{"type":"string"},"maxItems":100,"type":"array","uniqueItems":true},"wait_ms":{"maximum":20000,"minimum":0,"type":"integer"}},"required":["invite_ids"],"type":"object"},"schema:identity.link_release:1":{"additionalProperties":false,"properties":{"grant_box":{"maxLength":65536,"minLength":1,"type":"string"},"invite_id":{"type":"string"}},"required":["invite_id","grant_box"],"type":"object"},"schema:identity.login_bind:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.login_complete:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.login_remove:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.note_archive:1":{"additionalProperties":false,"properties":{"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"}},"required":["name","expected_revision"],"type":"object"},"schema:identity.note_get:1":{"additionalProperties":false,"properties":{"name":{"type":"string"}},"required":["name"],"type":"object"},"schema:identity.note_list:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.note_put:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"}},"required":["name","body"],"type":"object"},"schema:identity.note_put:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["name","body","resource_id","revision_id","content_signature","content_created_at"],"type":"object"},"schema:identity.note_restore:1":{"additionalProperties":false,"properties":{"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"}},"required":["name","expected_revision"],"type":"object"},"schema:identity.oauth_approve:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.oauth_request:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.personal_put:1":{"additionalProperties":false,"properties":{"body":{"type":"string"},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"kind":{"enum":["soul","agents"]}},"required":["kind","body"],"type":"object"},"schema:identity.personal_put:2":{"additionalProperties":false,"properties":{"body":{"type":"string"},"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"kind":{"enum":["soul","agents"]},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["kind","body","resource_id","revision_id","content_signature","content_created_at"],"type":"object"},"schema:identity.recover:1":{"additionalProperties":false,"properties":{"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject","public_key","possession_proof"],"type":"object"},"schema:identity.recovery_custodians:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.recovery_envelope_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:identity.recovery_envelope_list:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.recovery_envelope_register:1":{"additionalProperties":false,"properties":{"ciphertext_ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"custodian_refs":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":8,"type":"array","uniqueItems":true},"encryption_key_id":{"maxLength":160,"minLength":1,"type":"string"},"instructions_ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"policy_version":{"minimum":1,"type":"integer"},"purpose":{"const":"encryption-subkey-recovery"},"recipient_fingerprints":{"items":{"maxLength":160,"minLength":1,"type":"string"},"maxItems":8,"minItems":1,"type":"array","uniqueItems":true}},"required":["ciphertext_ref","encryption_key_id","policy_version","recipient_fingerprints","purpose"],"type":"object"},"schema:identity.recovery_policy_get:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:identity.recovery_policy_set:1":{"additionalProperties":false,"properties":{"encryption_key_id":{"maxLength":160,"minLength":1,"type":"string"},"expected_version":{"minimum":0,"type":"integer"},"recipients":{"items":{"additionalProperties":false,"properties":{"custodian_ref":{"maxLength":160,"minLength":1,"type":"string"},"recipient":{"type":"string"}},"required":["recipient"],"type":"object"},"maxItems":8,"type":"array"}},"required":["expected_version","encryption_key_id","recipients"],"type":"object"},"schema:identity.recovery_policy_set:2":{"additionalProperties":false,"properties":{"encryption_key_id":{"maxLength":160,"minLength":1,"type":"string"},"expected_version":{"minimum":0,"type":"integer"},"recipients":{"items":{"additionalProperties":false,"properties":{"custodian_ref":{"maxLength":160,"minLength":1,"type":"string"},"recipient":{"type":"string"}},"required":["recipient"],"type":"object"},"maxItems":8,"type":"array"}},"required":["expected_version","encryption_key_id","recipients"],"type":"object"},"schema:identity.register:1":{"additionalProperties":false,"properties":{"handle":{"type":"string"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["handle","public_key"],"type":"object"},"schema:identity.register:2":{"additionalProperties":false,"properties":{"encryption_recipient":{"type":"string"},"handle":{"type":"string"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["handle","public_key","encryption_recipient"],"type":"object"},"schema:identity.rename:1":{"additionalProperties":false,"properties":{"handle":{"type":"string"}},"required":["handle"],"type":"object"},"schema:identity.session_approve:1":{"additionalProperties":false,"properties":{"decision":{"enum":["approve","deny"]},"user_code":{"type":"string"}},"required":["user_code","decision"],"type":"object"},"schema:identity.session_request:1":{"additionalProperties":false,"properties":{"user_code":{"type":"string"}},"required":["user_code"],"type":"object"},"schema:identity.soul_visibility:1":{"additionalProperties":false,"properties":{"visibility":{"enum":["private","public"]}},"required":["visibility"],"type":"object"},"schema:identity.ssh_certificates:1":{"additionalProperties":false,"properties":{"certificates":{"items":{"maxLength":160,"minLength":1,"type":"string"},"type":"array","uniqueItems":true},"key_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["key_id","certificates"],"type":"object"},"schema:identity.ssh_key_add:1":{"additionalProperties":false,"properties":{"ceiling":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"type":"string"}},"required":["public_key","proof","ceiling"],"type":"object"},"schema:identity.ssh_key_revoke:1":{"additionalProperties":false,"properties":{"key_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["key_id"],"type":"object"},"schema:identity.temporary:1":{"additionalProperties":false,"properties":{"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["nonce"],"type":"object"},"schema:identity.temporary:2":{"additionalProperties":false,"properties":{"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"recovery_secret":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["nonce","recovery_secret"],"type":"object"},"schema:identity.temporary:3":{"additionalProperties":false,"properties":{"encryption_recipient":{"type":"string"},"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"recovery_secret":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["nonce","recovery_secret","public_key","encryption_recipient","possession_proof"],"type":"object"},"schema:identity.todo_archive:1":{"additionalProperties":false,"properties":{"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"}},"required":["name","expected_revision"],"type":"object"},"schema:identity.todo_get:1":{"additionalProperties":false,"properties":{"name":{"type":"string"}},"required":["name"],"type":"object"},"schema:identity.todo_list:1":{"additionalProperties":false,"properties":{"after_name":{"type":"string"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:identity.todo_put:1":{"additionalProperties":false,"properties":{"description":{"type":"string"},"due_at":{"type":["string","null"]},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"},"priority":{"enum":["low","neutral","high"]},"related_resource":{"anyOf":[{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},{"type":"null"}]},"status":{"enum":["pending","in_progress","done"]},"title":{"type":"string"}},"required":["name","title"],"type":"object"},"schema:identity.todo_put:2":{"additionalProperties":false,"properties":{"content_created_at":{"type":"string"},"content_signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"description":{"type":"string"},"due_at":{"type":["string","null"]},"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"},"priority":{"enum":["low","neutral","high"]},"related_resource":{"anyOf":[{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},{"type":"null"}]},"resource_id":{"maxLength":160,"minLength":1,"type":"string"},"revision_id":{"maxLength":160,"minLength":1,"type":"string"},"status":{"enum":["pending","in_progress","done"]},"title":{"type":"string"}},"required":["name","title","resource_id","revision_id","content_signature","content_created_at"],"type":"object"},"schema:identity.todo_restore:1":{"additionalProperties":false,"properties":{"expected_revision":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"}},"required":["name","expected_revision"],"type":"object"},"schema:identity.token_create:1":{"additionalProperties":false,"properties":{"ceiling":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"ttl":{"maximum":86400,"minimum":1,"type":"integer"}},"required":["nonce","ceiling","ttl"],"type":"object"},"schema:identity.token_create:2":{"additionalProperties":false,"properties":{"ceiling":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"recovery_secret":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"ttl":{"maximum":86400,"minimum":1,"type":"integer"}},"required":["nonce","ceiling","ttl","recovery_secret"],"type":"object"},"schema:identity.token_create:3":{"additionalProperties":false,"properties":{"ceiling":{"items":{"additionalProperties":false,"properties":{"capability":{"maxLength":160,"minLength":1,"type":"string"},"constraints":{"type":"object"},"operations":{"items":{"maxLength":160,"minLength":1,"type":"string"},"minItems":1,"type":"array","uniqueItems":true},"scope":{"additionalProperties":false,"properties":{"descendants":{"type":"boolean"},"resource_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource_id"],"type":"object"},"version":{"const":1}},"required":["capability","version","scope","operations","constraints"],"type":"object"},"type":"array"},"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"previous_credential":{"maxLength":160,"minLength":1,"type":"string"},"recovery_secret":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"ttl":{"maximum":86400,"minimum":1,"type":"integer"}},"required":["nonce","ttl","recovery_secret"],"type":"object"},"schema:identity.token_recover:1":{"additionalProperties":false,"properties":{"credential_id":{"type":"string"},"new_recovery_secret":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"original_request_id":{"type":"string"},"recovery_secret":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["credential_id","original_request_id","recovery_secret","nonce","new_recovery_secret"],"type":"object"},"schema:identity.token_rotate:1":{"additionalProperties":false,"properties":{"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["nonce"],"type":"object"},"schema:identity.token_rotate:2":{"additionalProperties":false,"properties":{"nonce":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"recovery_secret":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["nonce","recovery_secret"],"type":"object"},"schema:identity.upgrade:1":{"additionalProperties":false,"properties":{"handle":{"type":"string"},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["handle","public_key","possession_proof"],"type":"object"},"schema:identity.upgrade:2":{"additionalProperties":false,"properties":{"encryption_recipient":{"type":"string"},"handle":{"type":"string"},"possession_proof":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"},"public_key":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["handle","public_key","possession_proof","encryption_recipient"],"type":"object"},"schema:identity.upgrade_result:1":{"additionalProperties":false,"properties":{"upgrade_request_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["upgrade_request_id"],"type":"object"},"schema:job.get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:keystore.get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:keystore.list:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":200,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:keystore.put:1":{"additionalProperties":false,"properties":{"ciphertext":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"format":{"enum":["msg-x25519-v1","age","openpgp"]},"id":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["name","format"],"type":"object"},"schema:keystore.put:2":{"additionalProperties":false,"properties":{"ciphertext":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"encryption_key_id":{"maxLength":160,"minLength":1,"type":"string"},"format":{"enum":["msg-x25519-v1","age","openpgp"]},"id":{"maxLength":160,"minLength":1,"type":"string"},"name":{"type":"string"},"source":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["name","format"],"type":"object"},"schema:money.balance:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:money.banks:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:money.entitlements:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:money.ledger:1":{"additionalProperties":false,"properties":{"cursor":{"minimum":0,"type":"integer"},"limit":{"maximum":100,"minimum":1,"type":"integer"}},"required":[],"type":"object"},"schema:money.offers:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:money.offers:2":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:money.public_balance:1":{"additionalProperties":false,"properties":{"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:money.public_ledger:1":{"additionalProperties":false,"properties":{"cursor":{"minimum":0,"type":"integer"},"limit":{"maximum":100,"minimum":1,"type":"integer"},"subject_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject_id"],"type":"object"},"schema:money.purchase_cancel:1":{"additionalProperties":false,"properties":{"purchase_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["purchase_id"],"type":"object"},"schema:money.purchase_get:1":{"additionalProperties":false,"properties":{"purchase_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["purchase_id"],"type":"object"},"schema:money.purchase_get:2":{"additionalProperties":false,"properties":{"purchase_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["purchase_id"],"type":"object"},"schema:money.purchase_settle:1":{"additionalProperties":false,"properties":{"purchase_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["purchase_id"],"type":"object"},"schema:money.redeem:1":{"additionalProperties":false,"properties":{"currency_id":{"const":"primary"},"offer_id":{"maxLength":160,"minLength":1,"type":"string"},"price_revision":{"maxLength":160,"minLength":1,"type":"string"},"quantity":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["offer_id","quantity","currency_id","price_revision"],"type":"object"},"schema:money.redeem:2":{"additionalProperties":false,"properties":{"currency_id":{"const":"primary"},"defer":{"type":"boolean"},"offer_id":{"maxLength":160,"minLength":1,"type":"string"},"price_revision":{"maxLength":160,"minLength":1,"type":"string"},"quantity":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["offer_id","quantity","currency_id","price_revision"],"type":"object"},"schema:money.redeem:3":{"additionalProperties":false,"properties":{"currency_id":{"const":"primary"},"listing_revision":{"maxLength":160,"minLength":1,"type":"string"},"offer_id":{"maxLength":160,"minLength":1,"type":"string"},"offer_snapshot_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"price_revision":{"maxLength":160,"minLength":1,"type":"string"},"quantity":{"maximum":9223372036854775807,"minimum":1,"type":"integer"},"settlement_policy":{"const":"deterministic-entitlement-v1"},"total_price_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["offer_id","quantity","currency_id","price_revision","listing_revision","offer_snapshot_digest","total_price_minor","settlement_policy"],"type":"object"},"schema:money.state:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:money.transfer:1":{"additionalProperties":false,"properties":{"amount_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"},"currency_id":{"const":"primary"},"from_subject":{"maxLength":160,"minLength":1,"type":"string"},"reference":{"maxLength":160,"type":"string"},"to_subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["to_subject","currency_id","amount_minor"],"type":"object"},"schema:money.visibility_set:1":{"additionalProperties":false,"properties":{"visibility":{"enum":["private","public"]}},"required":["visibility"],"type":"object"},"schema:network-constraints":{"additionalProperties":false,"properties":{"hosts":{"items":{"type":"string"},"type":"array"},"max_redirects":{"minimum":0,"type":"integer"},"max_response_bytes":{"minimum":1,"type":"integer"},"methods":{"items":{"enum":["GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS"]},"type":"array"},"ports":{"items":{"maximum":65535,"minimum":1,"type":"integer"},"type":"array"},"schemes":{"items":{"enum":["http","https"]},"type":"array"},"timeout_ms":{"minimum":1,"type":"integer"}},"type":"object"},"schema:operation-result":{"additionalProperties":false,"properties":{"data":{"type":["object","null"]},"output":{"anyOf":[{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},{"type":"null"}]},"resources":{"items":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"type":"array"}},"required":["resources","data","output"],"type":"object"},"schema:orders.arbitrator_conflict:1":{"additionalProperties":false,"properties":{"party":{"maxLength":160,"minLength":1,"type":"string"}},"required":["party"],"type":"object"},"schema:orders.buy:1":{"additionalProperties":false,"properties":{"currency_id":{"const":"primary"},"listing_id":{"maxLength":160,"minLength":1,"type":"string"},"listing_revision":{"maxLength":160,"minLength":1,"type":"string"},"quantity":{"maximum":1000000000,"minimum":1,"type":"integer"},"total_price_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["listing_id","listing_revision","quantity","currency_id","total_price_minor"],"type":"object"},"schema:orders.buy:2":{"additionalProperties":false,"properties":{"auto_accept":{"type":"boolean"},"currency_id":{"const":"primary"},"email":{"maxLength":254,"minLength":3,"type":"string"},"listing_id":{"maxLength":160,"minLength":1,"type":"string"},"listing_revision":{"maxLength":160,"minLength":1,"type":"string"},"package_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"quantity":{"maximum":1000000000,"minimum":1,"type":"integer"},"total_price_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["listing_id","listing_revision","quantity","currency_id","total_price_minor","package_digest"],"type":"object"},"schema:orders.buy:3":{"additionalProperties":false,"properties":{"currency_id":{"const":"primary"},"email":{"maxLength":254,"minLength":3,"type":"string"},"listing_id":{"maxLength":160,"minLength":1,"type":"string"},"listing_revision":{"maxLength":160,"minLength":1,"type":"string"},"quantity":{"maximum":1000000000,"minimum":1,"type":"integer"},"total_price_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["listing_id","listing_revision","quantity","currency_id","total_price_minor"],"type":"object"},"schema:orders.buy:4":{"additionalProperties":false,"properties":{"currency_id":{"const":"primary"},"email":{"maxLength":254,"minLength":3,"type":"string"},"listing_id":{"maxLength":160,"minLength":1,"type":"string"},"listing_revision":{"maxLength":160,"minLength":1,"type":"string"},"quantity":{"maximum":1000000000,"minimum":1,"type":"integer"},"total_price_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["listing_id","listing_revision","quantity","currency_id","total_price_minor"],"type":"object"},"schema:orders.cancel:1":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:orders.cancel:2":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:orders.contract:1":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:orders.create:1":{"additionalProperties":false,"properties":{"currency_id":{"const":"primary"},"email":{"maxLength":254,"minLength":3,"type":"string"},"listing_id":{"maxLength":160,"minLength":1,"type":"string"},"listing_revision":{"maxLength":160,"minLength":1,"type":"string"},"quantity":{"maximum":1000000000,"minimum":1,"type":"integer"},"total_price_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["listing_id","listing_revision","quantity","currency_id","total_price_minor"],"type":"object"},"schema:orders.create:2":{"additionalProperties":false,"properties":{"currency_id":{"const":"primary"},"email":{"maxLength":254,"minLength":3,"type":"string"},"listing_id":{"maxLength":160,"minLength":1,"type":"string"},"listing_revision":{"maxLength":160,"minLength":1,"type":"string"},"quantity":{"maximum":1000000000,"minimum":1,"type":"integer"},"total_price_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["listing_id","listing_revision","quantity","currency_id","total_price_minor"],"type":"object"},"schema:orders.dispute_appeal:1":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["case_id"],"type":"object"},"schema:orders.dispute_evidence:1":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"},"ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"visibility":{"enum":["panel","parties"]}},"required":["case_id","visibility","ref"],"type":"object"},"schema:orders.dispute_evidence_get:1":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"},"evidence_id":{"maxLength":160,"minLength":1,"type":"string"},"length":{"maximum":65536,"minimum":1,"type":"integer"},"offset":{"minimum":0,"type":"integer"}},"required":["case_id","evidence_id"],"type":"object"},"schema:orders.dispute_execute:1":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"},"decision_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["case_id","decision_id"],"type":"object"},"schema:orders.dispute_get:1":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["case_id"],"type":"object"},"schema:orders.dispute_open:1":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"},"reason":{"enum":["quality","not_as_described","wrong_ciphertext","delivery_missing","delivery_digest_mismatch","package_missing","package_digest_mismatch","delivery_timeout","other"]}},"required":["order_id","reason"],"type":"object"},"schema:orders.dispute_rationale:1":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"},"ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","revision"],"type":"object"}},"required":["case_id","ref"],"type":"object"},"schema:orders.dispute_revoke:1":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"},"decision_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["case_id","decision_id"],"type":"object"},"schema:orders.dispute_statement:1":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"},"text":{"maxLength":4096,"minLength":1,"type":"string"},"visibility":{"enum":["panel","parties"]}},"required":["case_id","visibility","text"],"type":"object"},"schema:orders.dispute_summary:1":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["case_id"],"type":"object"},"schema:orders.dispute_vote:1":{"additionalProperties":false,"properties":{"proposal":{"additionalProperties":false,"properties":{"case_id":{"maxLength":160,"minLength":1,"type":"string"},"expires_at":{"maxLength":40,"type":"string"},"order_id":{"maxLength":160,"minLength":1,"type":"string"},"outcome":{"enum":["release","refund","split"]},"policy_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"rationale_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"rationale_ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","revision"],"type":"object"},"refund_minor":{"maximum":9223372036854775807,"minimum":0,"type":"integer"},"round":{"maximum":1,"minimum":0,"type":"integer"}},"required":["case_id","order_id","round","policy_digest","outcome","refund_minor","expires_at","rationale_ref","rationale_digest"],"type":"object"},"signature":{"additionalProperties":false,"properties":{"algorithm":{"const":"ed25519"},"key_id":{"maxLength":160,"minLength":1,"type":"string"},"value":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"}},"required":["key_id","algorithm","value"],"type":"object"}},"required":["proposal","signature"],"type":"object"},"schema:orders.email_bind:1":{"additionalProperties":false,"properties":{"email":{"maxLength":254,"minLength":3,"type":"string"},"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id","email"],"type":"object"},"schema:orders.email_disable:1":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:orders.email_verify:1":{"additionalProperties":false,"properties":{"challenge_id":{"maxLength":160,"minLength":1,"type":"string"},"order_id":{"maxLength":160,"minLength":1,"type":"string"},"token":{"maxLength":43,"minLength":43,"type":"string"}},"required":["order_id","challenge_id","token"],"type":"object"},"schema:orders.fund:1":{"additionalProperties":false,"properties":{"currency_id":{"const":"primary"},"order_digest":{"pattern":"^sha256:[a-f0-9]{64}$","type":"string"},"order_id":{"maxLength":160,"minLength":1,"type":"string"},"total_price_minor":{"maximum":9223372036854775807,"minimum":1,"type":"integer"}},"required":["order_id","order_digest","total_price_minor","currency_id"],"type":"object"},"schema:orders.get:1":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:orders.get:2":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"},"source":{"enum":["store_order","legacy_purchase"]}},"required":["order_id"],"type":"object"},"schema:orders.list:1":{"additionalProperties":false,"properties":{"limit":{"maximum":100,"minimum":1,"type":"integer"},"role":{"enum":["buy","sell"]},"status":{"enum":["open","completed","disputed"]}},"required":[],"type":"object"},"schema:orders.list:2":{"additionalProperties":false,"properties":{"limit":{"maximum":100,"minimum":1,"type":"integer"},"role":{"enum":["buy","sell"]},"status":{"enum":["open","completed","disputed"]}},"required":[],"type":"object"},"schema:orders.payment:1":{"additionalProperties":false,"properties":{"order_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["order_id"],"type":"object"},"schema:query.save:1":{"additionalProperties":false,"properties":{"query_ref":{"type":"string"}},"required":["query_ref"],"type":"object"},"schema:query.saved_archive:1":{"additionalProperties":false,"properties":{"ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","revision"],"type":"object"}},"required":["ref"],"type":"object"},"schema:query.saved_get:1":{"additionalProperties":false,"properties":{"ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","revision"],"type":"object"}},"required":["ref"],"type":"object"},"schema:saved-query-record:1":{"additionalProperties":false,"properties":{"created_at":{"type":"string"},"descriptor":{"additionalProperties":false,"properties":{"arguments":{"type":"object"},"contract_version":{"minimum":1,"type":"integer"},"operation":{"enum":["discovery.read_query","discovery.lexical_search"]}},"required":["operation","contract_version","arguments"],"type":"object"},"descriptor_digest":{"type":"string"},"principal":{"type":"object"},"source_digest":{"type":"string"},"subject":{"maxLength":160,"minLength":1,"type":"string"}},"required":["subject","created_at","source_digest","descriptor_digest","principal","descriptor"],"type":"object"},"schema:sharing.grant:1":{"additionalProperties":false,"properties":{"expires_at":{"type":"string"},"grantee":{"maxLength":160,"minLength":1,"type":"string"},"resource":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource","grantee","expires_at"],"type":"object"},"schema:sharing.grant:2":{"additionalProperties":false,"properties":{"allow_reshare":{"type":"boolean"},"constraints":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"expires_at":{"type":"string"},"grantee":{"maxLength":160,"minLength":1,"type":"string"},"grantee_kind":{"enum":["user","group"]},"operations":{"items":{"enum":["read"]},"minItems":1,"type":"array","uniqueItems":true},"resource":{"maxLength":160,"minLength":1,"type":"string"},"source_grant_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource","grantee","grantee_kind","operations","expires_at"],"type":"object"},"schema:sharing.link_create:1":{"additionalProperties":false,"properties":{"expires_at":{"type":"string"},"resource":{"maxLength":160,"minLength":1,"type":"string"},"verifier":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"}},"required":["resource","verifier","expires_at"],"type":"object"},"schema:sharing.link_list:1":{"additionalProperties":false,"properties":{"resource":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource"],"type":"object"},"schema:sharing.link_read:1":{"additionalProperties":false,"properties":{"link_id":{"maxLength":160,"minLength":1,"type":"string"},"token":{"type":"string"}},"required":["link_id","token"],"type":"object"},"schema:sharing.link_revoke:1":{"additionalProperties":false,"properties":{"link_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["link_id"],"type":"object"},"schema:sharing.list:1":{"additionalProperties":false,"properties":{"resource":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource"],"type":"object"},"schema:sharing.list:2":{"additionalProperties":false,"properties":{"resource":{"maxLength":160,"minLength":1,"type":"string"}},"required":["resource"],"type":"object"},"schema:sharing.revoke:1":{"additionalProperties":false,"properties":{"grant_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["grant_id"],"type":"object"},"schema:sharing.revoke:2":{"additionalProperties":false,"properties":{"grant_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["grant_id"],"type":"object"},"schema:store.listing_create:1":{"additionalProperties":false,"properties":{"currency_id":{"maxLength":160,"minLength":1,"type":"string"},"delivery_mode":{"enum":["managed_instant","sealed_manual","service"]},"dispute_policy":{"maxLength":80,"minLength":1,"type":"string"},"escrow_policy":{"maxLength":80,"minLength":1,"type":"string"},"expires_at":{"type":["string","null"]},"item_kind":{"enum":["file","text","secret","bundle","entitlement","service"]},"mode":{"enum":["sale","bounty"]},"name":{"maxLength":120,"minLength":1,"type":"string"},"price_minor":{"minimum":0,"type":"integer"},"quantity":{"minimum":0,"type":"integer"},"terms":{"maxLength":16384,"minLength":1,"type":"string"}},"required":["name","item_kind","price_minor","currency_id","quantity","delivery_mode","escrow_policy","dispute_policy","terms"],"type":"object"},"schema:store.listing_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:store.listing_get:2":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"},"source":{"enum":["resource","server_offer"]}},"required":["id"],"type":"object"},"schema:store.listing_update:1":{"additionalProperties":false,"properties":{"expires_at":{"type":["string","null"]},"id":{"maxLength":160,"minLength":1,"type":"string"},"package_ref":{"type":["string","null"]},"price_minor":{"minimum":0,"type":"integer"},"quantity":{"minimum":0,"type":"integer"},"state":{"enum":["draft","active","paused","closed"]},"terms":{"maxLength":16384,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:store.package_deposit:1":{"additionalProperties":false,"properties":{"listing_id":{"maxLength":160,"minLength":1,"type":"string"},"listing_revision":{"maxLength":160,"minLength":1,"type":"string"},"manifest":{"type":"object"},"payload_refs":{"items":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"maxItems":32,"minItems":1,"type":"array"}},"required":["listing_id","listing_revision","manifest","payload_refs"],"type":"object"},"schema:store.package_get:1":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id"],"type":"object"},"schema:system.config:1":{"additionalProperties":false,"properties":{"values":{"additionalProperties":false,"properties":{"accept_writes":{"type":"boolean"},"cleanup_enabled":{"type":"boolean"},"status_message":{"maxLength":300,"type":"string"}},"required":[],"type":"object"}},"required":["values"],"type":"object"},"schema:system.inspect:1":{"additionalProperties":false,"properties":{},"required":[],"type":"object"},"schema:system.maintenance:1":{"additionalProperties":false,"properties":{"action":{"enum":["cleanup_expired","rebuild_search","collect_garbage"]}},"required":["action"],"type":"object"},"schema:system.maintenance:2":{"additionalProperties":false,"properties":{"action":{"enum":["cleanup_expired","rebuild_search","collect_garbage","deliver_due_todos"]}},"required":["action"],"type":"object"},"schema:system.share_links_set:1":{"additionalProperties":false,"properties":{"enabled":{"type":"boolean"}},"required":["enabled"],"type":"object"},"schema:tool.curl:input":{"additionalProperties":false,"properties":{"body":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"body_ref":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"},"headers":{"additionalProperties":{"maxLength":4096,"type":"string"},"maxProperties":30,"type":"object"},"method":{"enum":["GET","HEAD","POST","PUT","PATCH","DELETE","OPTIONS"]},"url":{"maxLength":8192,"minLength":1,"type":"string"}},"required":["url"],"type":"object"},"schema:tool.curl:output":{"type":"object"},"schema:tool.dns:input":{"additionalProperties":false,"properties":{"name":{"maxLength":253,"minLength":1,"type":"string"},"type":{"enum":["A","AAAA","TXT","MX","NS","CNAME","SRV"]}},"required":["name","type"],"type":"object"},"schema:tool.dns:output":{"type":"object"},"schema:tool.run:1":{"additionalProperties":false,"properties":{"arguments":{"type":"object"},"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"maxLength":160,"minLength":1,"type":"string"}},"required":["id","arguments"],"type":"object"},"schema:transfer.cancel:1":{"additionalProperties":false,"properties":{"transfer_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["transfer_id"],"type":"object"},"schema:transfer.open:1":{"additionalProperties":false,"properties":{"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"direction":{"enum":["upload","download"]},"max_path_bytes":{"minimum":1,"type":"integer"},"max_request_bytes":{"minimum":1,"type":"integer"},"max_response_bytes":{"minimum":1,"type":"integer"},"media_type":{"type":"string"},"requested_part_bytes":{"minimum":1,"type":"integer"},"size":{"maximum":9223372036854775807,"minimum":0,"type":"integer"},"target":{"additionalProperties":false,"properties":{"id":{"maxLength":160,"minLength":1,"type":"string"},"revision":{"type":["string","null"]}},"required":["id"],"type":"object"}},"required":["direction"],"type":"object"},"schema:transfer.part_get:1":{"additionalProperties":false,"properties":{"length":{"maximum":9223372036854775807,"minimum":0,"type":"integer"},"offset":{"maximum":9223372036854775807,"minimum":0,"type":"integer"},"transfer_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["transfer_id","offset","length"],"type":"object"},"schema:transfer.part_put:1":{"additionalProperties":false,"properties":{"data":{"pattern":"^[A-Za-z0-9_-]*$","type":"string"},"digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"offset":{"maximum":9223372036854775807,"minimum":0,"type":"integer"},"transfer_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["transfer_id","offset","data","digest"],"type":"object"},"schema:transfer.query_get:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"query_ref":{"type":"string"}},"required":["query_ref"],"type":"object"},"schema:transfer.query_seal:1":{"additionalProperties":false,"properties":{"transfer_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["transfer_id"],"type":"object"},"schema:transfer.seal:1":{"additionalProperties":false,"properties":{"final_digest":{"pattern":"^sha256:[0-9a-f]{64}$","type":"string"},"final_size":{"maximum":9223372036854775807,"minimum":0,"type":"integer"},"transfer_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["transfer_id","final_size","final_digest"],"type":"object"},"schema:transfer.status:1":{"additionalProperties":false,"properties":{"cursor":{"type":"string"},"limit":{"maximum":500,"minimum":1,"type":"integer"},"transfer_id":{"maxLength":160,"minLength":1,"type":"string"}},"required":["transfer_id"],"type":"object"}},"temporary_ceiling":[{"capability":"batch.basic","constraints":{},"operations":["batch.atomic@1","batch.independent@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"communication.basic","constraints":{},"operations":["communication.changes@1","communication.event@1","communication.events@1","communication.handoff_create@1","communication.handoff_decide@1","communication.handoff_get@1","communication.handoff_list@1","communication.inbox@1","communication.lease_acquire@1","communication.lease_get@1","communication.lease_list@1","communication.lease_release@1","communication.lease_renew@1","communication.outbox@1","communication.send@1","communication.unwatch@1","communication.watch@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"discovery.basic","constraints":{},"operations":["achievement.list@1","discovery.capabilities@1","discovery.diff@1","discovery.get@1","discovery.list@1","discovery.operations@1","discovery.raw@1","discovery.references@1","discovery.resolve@1","discovery.schema@1","discovery.search@1","job.get@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"discussion.basic","constraints":{},"operations":["discussion.ack@1","discussion.acks@1","discussion.like@1","discussion.quote@1","discussion.reading_manifest@1","discussion.reading_prove@1","discussion.readings@1","discussion.reply@1","discussion.repost@1","discussion.thread@1","discussion.unlike@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"identity.basic","constraints":{},"operations":["achievement.answer@1","achievement.finish@1","achievement.pin@1","achievement.reorder@1","achievement.start@1","achievement.unpin@1","identity.custodial_create@1","identity.custodial_create@2","identity.custodial_migration_get@1","identity.custodial_rewrap_ack@1","identity.custodial_rewrap_ack@2","identity.custodial_rewrap_entry@1","identity.custodial_rewrap_revision@1","identity.custodial_status@1","identity.custodial_upgrade_finish@1","identity.custodial_upgrade_finish@2","identity.custodial_upgrade_inventory@1","identity.custodial_upgrade_start@1","identity.rename@1","identity.session_approve@1","identity.session_request@1","identity.temporary@1","identity.temporary@2","identity.temporary@3","identity.token_rotate@1","identity.token_rotate@2","identity.upgrade@1","identity.upgrade@2"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"keystore.basic","constraints":{},"operations":["keystore.get@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"resource.basic","constraints":{},"operations":["content.archive@1","content.attach@1","content.file_put@1","content.post_create@1","content.post_edit@1","content.public_board_update@1","content.restore@1","content.topic_create@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1},{"capability":"transfer.basic","constraints":{},"operations":["transfer.cancel@1","transfer.open@1","transfer.part_get@1","transfer.part_put@1","transfer.seal@1","transfer.status@1"],"scope":{"descendants":true,"resource_id":"r_root"},"version":1}],"unicode_version":"17.0.0","version":1} diff --git a/rust/crates/msg-crypto/src/age_recipient.rs b/rust/crates/msg-crypto/src/age_recipient.rs index 16367f02..efcf8991 100644 --- a/rust/crates/msg-crypto/src/age_recipient.rs +++ b/rust/crates/msg-crypto/src/age_recipient.rs @@ -2,6 +2,7 @@ use msg_core::{Error, Result}; use sha2::{Digest, Sha256}; use x25519_dalek::{PublicKey, StaticSecret}; +use zeroize::Zeroizing; const ALPHABET: &[u8] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l"; @@ -22,34 +23,36 @@ fn polymod(values: impl IntoIterator) -> u32 { check } -fn prefix() -> Vec { - b"age" +fn prefix(hrp: &str) -> Vec { + hrp.as_bytes() .iter() .map(|c| c >> 5) .chain([0]) - .chain(b"age".iter().map(|c| c & 31)) + .chain(hrp.as_bytes().iter().map(|c| c & 31)) .collect() } -pub fn public_from_recipient(recipient: &str) -> Result<[u8; 32]> { +fn decode(value: &str, hrp: &str) -> Result> { let invalid = Error("invalid_age_recipient"); - if recipient.len() != 62 || !recipient.starts_with("age1") { + if value.len() != hrp.len() + 59 || !value.starts_with(&format!("{hrp}1")) { return Err(invalid); } - let values: Vec = recipient.as_bytes()[4..] - .iter() - .map(|c| { - ALPHABET - .iter() - .position(|v| v == c) - .map(|i| i as u8) - .ok_or(invalid) - }) - .collect::>()?; - if polymod(prefix().into_iter().chain(values.iter().copied())) != 1 { + let values = Zeroizing::new( + value.as_bytes()[hrp.len() + 1..] + .iter() + .map(|c| { + ALPHABET + .iter() + .position(|v| v == c) + .map(|i| i as u8) + .ok_or(invalid) + }) + .collect::>>()?, + ); + if polymod(prefix(hrp).into_iter().chain(values.iter().copied())) != 1 { return Err(invalid); } - let mut public = [0u8; 32]; + let mut public = Zeroizing::new([0u8; 32]); let mut accumulator = 0u32; let mut bits = 0; let mut index = 0; @@ -65,19 +68,24 @@ pub fn public_from_recipient(recipient: &str) -> Result<[u8; 32]> { if index != 32 || bits != 4 || accumulator & 15 != 0 { return Err(invalid); } + Ok(public) +} + +pub fn public_from_recipient(recipient: &str) -> Result<[u8; 32]> { + let public = *decode(recipient, "age")?; // A fixed nonsecret scalar is sufficient to reject every low-order point; // it is never used for message encryption or as an account identity. if !StaticSecret::from([42u8; 32]) .diffie_hellman(&PublicKey::from(public)) .was_contributory() { - return Err(invalid); + return Err(Error("invalid_age_recipient")); } Ok(public) } -pub fn recipient_from_public(public: &[u8; 32]) -> String { - let mut values = Vec::with_capacity(58); +fn encode(public: &[u8; 32], hrp: &str) -> String { + let mut values = Zeroizing::new(Vec::with_capacity(58)); let mut accumulator = 0u32; let mut bits = 0; for byte in public { @@ -92,7 +100,7 @@ pub fn recipient_from_public(public: &[u8; 32]) -> String { values.push(((accumulator << (5 - bits)) & 31) as u8); } let checksum = polymod( - prefix() + prefix(hrp) .into_iter() .chain(values.iter().copied()) .chain([0; 6]), @@ -100,11 +108,34 @@ pub fn recipient_from_public(public: &[u8; 32]) -> String { for index in (0..6).rev() { values.push(((checksum >> (5 * index)) & 31) as u8); } - let mut recipient = String::from("age1"); - recipient.extend(values.into_iter().map(|v| ALPHABET[usize::from(v)] as char)); + let mut recipient = format!("{hrp}1"); + recipient.extend(values.iter().map(|v| ALPHABET[usize::from(*v)] as char)); recipient } +pub fn recipient_from_public(public: &[u8; 32]) -> String { + encode(public, "age") +} + +pub fn identity_from_private(private: &[u8; 32]) -> Zeroizing { + let mut text = Zeroizing::new(encode(private, "age-secret-key-")); + text.make_ascii_uppercase(); + text +} + +pub fn private_from_identity(identity: &str) -> Result> { + if !identity.is_ascii() || identity.bytes().any(|byte| byte.is_ascii_lowercase()) { + return Err(Error("invalid_age_identity")); + } + let text = Zeroizing::new(identity.to_ascii_lowercase()); + decode(&text, "age-secret-key-").map_err(|_| Error("invalid_age_identity")) +} + +pub fn recipient_from_identity(identity: &str) -> Result { + let private = StaticSecret::from(*private_from_identity(identity)?); + Ok(recipient_from_public(&PublicKey::from(&private).to_bytes())) +} + pub fn encryption_key_id(public: &[u8; 32]) -> String { let mut hash = Sha256::new(); hash.update(b"msg-encryption-subkey-v1\0"); @@ -130,6 +161,14 @@ mod tests { "ek_69d193dcc1c5da912145b4309f9a14f39fdb88678b3f337166b128187b4a7402" ); assert_eq!(public_from_recipient(&recipient).unwrap(), public); + let identity = identity_from_private(&[1; 32]); + assert_eq!( + identity.as_str(), + "AGE-SECRET-KEY-1QYQSZQGPQYQSZQGPQYQSZQGPQYQSZQGPQYQSZQGPQYQSZQGPQYQSZ9K4CN" + ); + assert_eq!(*private_from_identity(&identity).unwrap(), [1; 32]); + assert_eq!(recipient_from_identity(&identity).unwrap(), recipient); + assert!(private_from_identity(&identity.to_ascii_lowercase()).is_err()); assert!(public_from_recipient(&recipient.to_uppercase()).is_err()); assert!(public_from_recipient(&recipient_from_public(&[0; 32])).is_err()); let mut changed = recipient.into_bytes(); diff --git a/rust/crates/msg-crypto/src/lib.rs b/rust/crates/msg-crypto/src/lib.rs index 02e3044a..866ac007 100644 --- a/rust/crates/msg-crypto/src/lib.rs +++ b/rust/crates/msg-crypto/src/lib.rs @@ -1,6 +1,7 @@ //! Native signing and recipient encryption, not authentication or authorization. pub mod age_recipient; pub mod sealed_box; +pub mod vault_box; use ed25519_dalek::{Signer as _, SigningKey, VerifyingKey}; use msg_core::{b64, unb64, Error, Result}; diff --git a/rust/crates/msg-crypto/src/vault_box.rs b/rust/crates/msg-crypto/src/vault_box.rs new file mode 100644 index 00000000..a2bc3361 --- /dev/null +++ b/rust/crates/msg-crypto/src/vault_box.rs @@ -0,0 +1,62 @@ +//! Bounded AES-256-GCM key storage. Callers supply a domain-separated AAD. +use aes_gcm::{aead::AeadInPlace, Aes256Gcm, KeyInit, Nonce}; +use msg_core::{b64, unb64, Error, Result}; +use rand_core::{OsRng, RngCore}; +use zeroize::Zeroizing; + +#[derive(Clone)] +pub struct SealedSecret { + pub nonce: String, + pub ciphertext: String, +} + +pub fn seal(key: &[u8; 32], aad: &[u8], secret: &[u8]) -> Result { + if secret.len() > 8176 || aad.len() > 8192 { + return Err(Error("invalid_vault_secret")); + } + let mut nonce = [0u8; 12]; + OsRng + .try_fill_bytes(&mut nonce) + .map_err(|_| Error("randomness_unavailable"))?; + let mut ciphertext = Zeroizing::new(secret.to_vec()); + Aes256Gcm::new_from_slice(key) + .map_err(|_| Error("invalid_vault_secret"))? + .encrypt_in_place(&Nonce::from(nonce), aad, &mut *ciphertext) + .map_err(|_| Error("invalid_vault_secret"))?; + Ok(SealedSecret { + nonce: b64(&nonce), + ciphertext: b64(&ciphertext), + }) +} + +pub fn open(key: &[u8; 32], aad: &[u8], sealed: &SealedSecret) -> Result>> { + let invalid = Error("invalid_vault_secret"); + let nonce: [u8; 12] = unb64(&sealed.nonce, 12) + .map_err(|_| invalid)? + .try_into() + .map_err(|_| invalid)?; + let mut secret = Zeroizing::new(unb64(&sealed.ciphertext, 8192).map_err(|_| invalid)?); + if secret.len() < 16 || aad.len() > 8192 { + return Err(invalid); + } + Aes256Gcm::new_from_slice(key) + .map_err(|_| invalid)? + .decrypt_in_place(&Nonce::from(nonce), aad, &mut *secret) + .map_err(|_| invalid)?; + Ok(secret) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn domain_and_key_are_authenticated() { + let sealed = seal(&[1; 32], b"subject/key/identity", &[2; 32]).unwrap(); + assert_eq!( + &*open(&[1; 32], b"subject/key/identity", &sealed).unwrap(), + &[2; 32] + ); + assert!(open(&[1; 32], b"subject/key/encryption", &sealed).is_err()); + assert!(open(&[3; 32], b"subject/key/identity", &sealed).is_err()); + } +} diff --git a/rust/crates/msg-identity/src/certificate_lifecycle.rs b/rust/crates/msg-identity/src/certificate_lifecycle.rs new file mode 100644 index 00000000..6b1cb970 --- /dev/null +++ b/rust/crates/msg-identity/src/certificate_lifecycle.rs @@ -0,0 +1,290 @@ +//! CSR creation, publication and revocation under current certificate authority. +use crate::{ + authentication::{Entry, Principal}, + authorization::{AccessRequirement, AuthorizationService, AuthorizationStore, Check}, + certificates::CertificateValidator, + lifecycle::{field, new_id, optional, output, resource, signature, text}, + models::*, + mutations::IdentityStore, + require, +}; +use msg_core::{digest, unb64, Error, Json, Result}; +use msg_crypto::{key_id, verify}; + +pub trait CertificateStore: IdentityStore { + fn authorization_store(&self) -> &dyn AuthorizationStore; + fn certificate_request(&self, id: &str) -> Result; + fn certificate_request_state(&self, id: &str) -> Result; + /// Enforce the existing 1024-pending / 8192-total CSR durability caps. + fn save_certificate_request(&mut self, request: &CertificateRequest) -> Result<()>; + fn transition_certificate_request(&mut self, state: &Json, expected: i64) -> Result<()>; + fn publish_certificate( + &mut self, + certificate: &Certificate, + csr: &str, + expected: i64, + ) -> Result<()>; + fn revoke_identity_certificate(&mut self, id: &str, audit: &Json) -> Result<()>; +} + +pub struct Service<'a> { + pub validator: &'a CertificateValidator, +} +impl Service<'_> { + pub fn supports(operation: &str, version: u32) -> bool { + version == 1 + && matches!( + operation, + "cert.request" | "cert.get" | "cert.cancel" | "cert.publish" | "cert.revoke" + ) + } + + pub fn handle( + &self, + store: &mut S, + authorizer: &mut AuthorizationService, + principal: &Principal, + request: &Json, + now: Timestamp, + ) -> Result { + store.require_live()?; + let name = text(request, "operation")?; + let version: u32 = decode(field(request, "contract_version")?)?; + require(Self::supports(name, version), "operation_not_found")?; + let operation = format!("{name}@{version}"); + let args = field(request, "arguments")?; + if name == "cert.get" { + let id = store.resolve_identity(text(args, "id")?)?; + authorizer.require( + principal, + Entry::Network, + now, + &[AccessRequirement { + resource_id: id.clone(), + operation, + check: Check::Read, + }], + None, + store.authorization_store(), + )?; + let object = store.resource(&id)?; + if object.kind == "csr" { + return output( + serde_json::json!({"request":store.certificate_request(&id)?,"state":store.certificate_request_state(&id)?}), + vec![], + ); + } + require(object.kind == "certificate", "not_a_certificate")?; + return output( + serde_json::json!({"certificate":store.certificate(&id)?,"revoked":store.certificate_revoked(&id)?}), + vec![], + ); + } + signature(principal)?; + let subject = principal + .subject() + .ok_or(Error("authentication_required"))?; + let actor = principal.actor().ok_or(Error("authentication_required"))?; + match name { + "cert.request" => { + authorizer.require_base(principal, &operation, subject, store, now)?; + self.request(store, principal, args, now) + } + "cert.cancel" => { + let id = store.resolve_identity(text(args, "id")?)?; + let request = store.certificate_request(&id)?; + require(request.applicant == subject, "csr_owner_required")?; + authorizer.require_base(principal, &operation, &id, store, now)?; + let state = store.certificate_request_state(&id)?; + let generation: i64 = decode(field(&state, "generation")?)?; + let mut updated = state.into_object()?; + updated.insert("status".into(), Json::string("cancelled")); + updated.insert( + "generation".into(), + wire( + &generation + .checked_add(1) + .ok_or(Error("invalid_generation"))?, + )?, + ); + store.transition_certificate_request(&Json::from_object(updated), generation)?; + output( + serde_json::json!({"status":"cancelled"}), + vec![ResourceRef { id, revision: None }], + ) + } + "cert.publish" => { + let csr = store.certificate_request(text(args, "csr_id")?)?; + let state = store.certificate_request_state(&csr.resource_id)?; + require(text(&state, "status")? == "pending", "csr_not_pending")?; + let generation: i64 = decode(field(&state, "generation")?)?; + let certificate: Certificate = decode(field(args, "certificate")?)?; + require( + certificate.issuer_id == actor && actor == subject && subject != "u_root", + "issuer_mismatch", + )?; + authorizer.ceiling(principal, &operation, &csr.resource_id, store, now)?; + require( + authorizer.has( + principal, + "cert.issue", + &operation, + &csr.resource_id, + store, + now, + )?, + "certificate_issue_required", + )?; + store.insert_identity_resource(&resource( + &certificate.resource_id, + "certificate", + &certificate.resource_id, + "t_certificates", + (&certificate.subject_id, &certificate.issuer_id), + "0444", + now, + )?)?; + self.validator + .validate_publication(&certificate, &csr, store, now)?; + store.publish_certificate(&certificate, &csr.resource_id, generation)?; + let mut credential = store.credential(&certificate.key_id)?; + if credential.ceiling.is_empty() { + credential.ceiling = certificate.grants.clone(); + let subject = store.subject(&credential.subject_id)?; + store.save_credential(&credential, subject.auth_version)?; + } + output( + serde_json::json!({"certificate_id":certificate.resource_id,"status":"issued"}), + vec![ResourceRef { + id: certificate.resource_id, + revision: None, + }], + ) + } + "cert.revoke" => { + let id = store.resolve_identity(text(args, "id")?)?; + let certificate = store.certificate(&id)?; + require(certificate.subject_id != "u_root", "local_only")?; + require( + authorizer.has(principal, "cert.revoke", &operation, &id, store, now)?, + "certificate_revoke_required", + )?; + let audit = wire( + &serde_json::json!({"event":{"id":new_id("e")?,"type":"cert.revoke","time":now, + "request_id":text(request,"request_id")?,"actor":actor,"subject":subject,"resources":[{"id":id}],"data":{"reason":field(args,"reason")?}}, + "authority":principal.certificates().iter().map(|id|ResourceRef{id:id.clone(),revision:None}).collect::>(), + "before_digest":digest(wire(&certificate)?.canonical()?.as_bytes()),"after_digest":null,"previous_digest":null,"entry_digest":"","result":"revoked"}), + )?; + store.revoke_identity_certificate(&id, &audit)?; + output( + serde_json::json!({"revoked":true}), + vec![ResourceRef { id, revision: None }], + ) + } + _ => Err(Error("operation_not_found")), + } + } + + fn request( + &self, + store: &mut S, + principal: &Principal, + args: &Json, + now: Timestamp, + ) -> Result { + let subject = principal + .subject() + .ok_or(Error("authentication_required"))?; + let actor = principal.actor().ok_or(Error("authentication_required"))?; + let issuer = store.resolve_identity(text(args, "requested_issuer")?)?; + require(store.resource(&issuer)?.kind == "user", "invalid_issuer")?; + let public = unb64(text(args, "public_key")?, 32)?; + let grants: Vec = decode(field(args, "grants")?)?; + for grant in &grants { + self.validator.validate_grant(grant, store)?; + } + let issuance = optional(args, "issuance")? + .filter(|value| !value.is_null()) + .map(decode::) + .transpose()?; + let kind: CertificateKind = decode(field(args, "kind")?)?; + require( + matches!( + kind, + CertificateKind::Identity | CertificateKind::Capability | CertificateKind::Ca + ), + "invalid_certificate_kind", + )?; + require( + (kind == CertificateKind::Ca) == issuance.is_some(), + "ca_issuance_required", + )?; + if let Some(policy) = &issuance { + for grant in &policy.issue_grants { + self.validator.validate_grant(grant, store)?; + } + } + let mut csr = CertificateRequest { + resource_id: new_id("csr")?, + applicant: subject.into(), + subject_id: subject.into(), + requested_issuer: issuer, + public_key: Verifier(public.clone()), + kind, + grants, + issuance, + requested_ttl_seconds: decode(field(args, "requested_ttl_seconds")?)?, + target_service: self.validator.service().into(), + delegation_depth: optional(args, "delegation_depth")? + .map(decode) + .transpose()? + .unwrap_or(0), + authority_sources: vec![], + request_digest: String::new(), + possession_proof: decode(field(args, "possession_proof")?)?, + }; + require(csr.requested_ttl_seconds > 0, "invalid_ttl")?; + let bytes = csr.signing_bytes()?; + csr.request_digest = digest(&bytes); + verify(&public, &bytes, &csr.possession_proof, "csr")?; + let key = key_id(&public); + match store.credential(&key) { + Ok(existing) => require(existing.subject_id == subject, "key_owner_mismatch")?, + Err(Error("credential_not_found")) => { + let owner = store.subject(subject)?; + store.save_credential( + &Credential { + id: key, + subject_id: subject.into(), + kind: CredentialKind::SigningKey, + verifier: Verifier(public), + ceiling: vec![], + not_before: now, + expires_at: None, + revoked_at: None, + source_credential_id: None, + }, + owner.auth_version, + )?; + } + Err(error) => return Err(error), + } + store.insert_identity_resource(&resource( + &csr.resource_id, + "csr", + &csr.resource_id, + "t_csr", + (subject, actor), + "0600", + now, + )?)?; + store.save_certificate_request(&csr)?; + output( + serde_json::json!({"csr_id":csr.resource_id,"request_digest":csr.request_digest,"status":"pending"}), + vec![ResourceRef { + id: csr.resource_id, + revision: None, + }], + ) + } +} diff --git a/rust/crates/msg-identity/src/lib.rs b/rust/crates/msg-identity/src/lib.rs index 5b6ea6ff..bef5803a 100644 --- a/rust/crates/msg-identity/src/lib.rs +++ b/rust/crates/msg-identity/src/lib.rs @@ -5,10 +5,12 @@ pub mod authentication; pub mod authorization; pub mod browser; +pub mod certificate_lifecycle; pub mod certificates; pub mod lifecycle; pub mod models; pub mod mutations; +pub mod vault; pub mod policy; pub mod sessions; pub mod store; diff --git a/rust/crates/msg-identity/src/lifecycle.rs b/rust/crates/msg-identity/src/lifecycle.rs index b6eeedda..7baac690 100644 --- a/rust/crates/msg-identity/src/lifecycle.rs +++ b/rust/crates/msg-identity/src/lifecycle.rs @@ -18,6 +18,7 @@ use chrono::Duration; use hmac::{Hmac, Mac}; use msg_core::{b64, digest, unb64, Error, Json, Result}; use msg_crypto::{age_recipient, key_id, verify, Ed25519Signer, Signature}; +use rand_core::{OsRng, RngCore}; use sha2::{Digest, Sha256}; use std::collections::BTreeSet; use subtle::ConstantTimeEq; @@ -45,15 +46,15 @@ struct Issuance<'a> { authority_source: Option<&'a Certificate>, } -fn field<'a>(value: &'a Json, name: &str) -> Result<&'a Json> { +pub(crate) fn field<'a>(value: &'a Json, name: &str) -> Result<&'a Json> { value.as_object()?.get(name).ok_or(Error("invalid_request")) } -fn text<'a>(value: &'a Json, name: &str) -> Result<&'a str> { +pub(crate) fn text<'a>(value: &'a Json, name: &str) -> Result<&'a str> { field(value, name)? .as_str() .map_err(|_| Error("invalid_request")) } -fn optional<'a>(value: &'a Json, name: &str) -> Result> { +pub(crate) fn optional<'a>(value: &'a Json, name: &str) -> Result> { Ok(value.as_object()?.get(name)) } fn after(now: Timestamp, ttl: u64) -> Result { @@ -63,10 +64,10 @@ fn after(now: Timestamp, ttl: u64) -> Result { .map(Timestamp) .ok_or(Error("invalid_ttl")) } -fn new_id(prefix: &str) -> Result { +pub(crate) fn new_id(prefix: &str) -> Result { Ok(format!("{prefix}_{}", random_identifier()?)) } -fn output(data: serde_json::Value, resources: Vec) -> Result { +pub(crate) fn output(data: serde_json::Value, resources: Vec) -> Result { wire(&serde_json::json!({"data":data,"resources":resources})) } fn owner(store: &dyn AuthorityStore, principal: &Principal) -> Result { @@ -78,7 +79,7 @@ fn owner(store: &dyn AuthorityStore, principal: &Principal) -> Result { require(!subject.local_only && id != "u_root", "local_only")?; Ok(subject) } -fn signature(principal: &Principal) -> Result<()> { +pub(crate) fn signature(principal: &Principal) -> Result<()> { require(principal.method() == "signature", "signature_required") } fn handle(handle: &str) -> Result<()> { @@ -113,7 +114,7 @@ fn token_id(request: &Json, subject: &str) -> Result { )[7..39] )) } -fn resource( +pub(crate) fn resource( id: &str, kind: &str, name: &str, @@ -134,9 +135,10 @@ fn resource( impl Service<'_> { pub fn supports(operation: &str, version: u32) -> bool { match operation { - "identity.register" | "identity.upgrade" | "identity.token_rotate" => { - (1..=2).contains(&version) - } + "identity.register" + | "identity.upgrade" + | "identity.token_rotate" + | "identity.custodial_create" => (1..=2).contains(&version), "identity.temporary" | "identity.token_create" => (1..=3).contains(&version), "identity.key_add" | "identity.key_revoke" @@ -147,6 +149,7 @@ impl Service<'_> { | "identity.encryption_key_get" | "identity.encryption_key_rotate" | "identity.certificate_renew" + | "identity.custodial_status" | "identity.upgrade_result" => version == 1, _ => false, } @@ -173,6 +176,11 @@ impl Service<'_> { require(version == 3, "temporary_dual_keys_required")?; self.temporary(store, principal, request, arguments, now) } + "identity.custodial_create" => { + require(version == 2, "credential_delivery_upgrade_required")?; + self.custodial_create(store, principal, request, arguments, now) + } + "identity.custodial_status" => self.custodial_status(store, principal, now), "identity.key_add" => self.key_add(store, principal, request, arguments, now), "identity.key_revoke" => self.key_revoke(store, principal, arguments, now), "identity.encryption_key_rotate" => { @@ -547,6 +555,91 @@ impl Service<'_> { ) } + fn custodial_create( + &self, + store: &mut S, + principal: &Principal, + request: &Json, + args: &Json, + now: Timestamp, + ) -> Result { + let id = principal.subject().ok_or(Error("invalid_bootstrap"))?; + require( + principal.actor() == Some(id) + && principal.method() == "token" + && id.starts_with("u_cust_"), + "invalid_bootstrap", + )?; + let mut signing_seed = Zeroizing::new([0u8; 32]); + OsRng + .try_fill_bytes(signing_seed.as_mut()) + .map_err(|_| Error("randomness_unavailable"))?; + let signer = Ed25519Signer::from_seed(&signing_seed); + let (age_private, public) = msg_crypto::sealed_box::generate_key()?; + let recipient = age_recipient::recipient_from_public(&public); + let subject = self.make_user( + store, + id, + text(args, "handle")?, + SubjectKind::Custodial, + now, + )?; + // Vault signing keys deliberately have no network authority of their own. + let (signing, encryption) = + self.add_keys(store, &subject, &signer.public_key(), &recipient, &[], now)?; + let credential = Credential { + id: principal + .credential_id() + .ok_or(Error("invalid_bootstrap"))? + .into(), + subject_id: id.into(), + kind: CredentialKind::Token, + verifier: Verifier(Sha256::digest(self.issued_token(request, id)?.as_ref()).to_vec()), + ceiling: self.temporary_ceiling.to_vec(), + not_before: now, + expires_at: Some(after(now, self.temporary_ttl)?), + revoked_at: None, + source_credential_id: None, + }; + store.save_credential(&credential, 0)?; + self.record_delivery(store, request, &credential, None, now)?; + store.insert_custodial_vault(&crate::vault::seal_keys( + self.token_secret, + id, + &signing_seed, + &age_private, + now, + )?)?; + output( + serde_json::json!({"subject_id":id,"credential_id":credential.id,"identity_key_id":signing,"encryption_key_id":encryption, + "encryption_recipient":recipient,"expires_at":credential.expires_at,"signature_source":"custodial","server_signable":true,"server_decryptable":true}), + vec![ResourceRef { + id: id.into(), + revision: None, + }], + ) + } + + fn custodial_status( + &self, + store: &S, + principal: &Principal, + now: Timestamp, + ) -> Result { + let subject = owner(store, principal)?; + require(subject.kind == SubjectKind::Custodial, "not_custodial")?; + let row = store + .vault_record(&subject.resource_id)? + .ok_or(Error("custodial_vault_unavailable"))?; + crate::vault::open_signer(store, self.token_secret, &subject.resource_id, now)?; + crate::vault::open_age_identity(store, self.token_secret, &subject.resource_id)?; + output( + serde_json::json!({"subject_id":subject.resource_id,"identity_key_id":row.signing_key_id,"encryption_key_id":row.encryption_key_id, + "signature_source":"custodial","server_signable":true,"server_decryptable":true}), + vec![], + ) + } + fn validate_ceiling( &self, store: &S, @@ -805,7 +898,7 @@ impl Service<'_> { let version: u32 = decode(field(request, "contract_version")?)?; let minimum = match operation { "identity.temporary" => 3, - "identity.token_create" | "identity.token_rotate" => 2, + "identity.token_create" | "identity.token_rotate" | "identity.custodial_create" => 2, "identity.token_recover" => 1, _ => return Err(Error("credential_delivery_upgrade_required")), }; @@ -863,7 +956,7 @@ impl Service<'_> { signature(principal)?; require(version >= 2, "credential_delivery_upgrade_required")?; let subject = owner(store, principal)?; - let ceiling = if version == 3 && optional(args, "ceiling")?.is_none() { + let ceiling: Vec = if version == 3 && optional(args, "ceiling")?.is_none() { let reads: BTreeSet = self .operations .iter() diff --git a/rust/crates/msg-identity/src/mutations.rs b/rust/crates/msg-identity/src/mutations.rs index e5a23ac9..88c5d221 100644 --- a/rust/crates/msg-identity/src/mutations.rs +++ b/rust/crates/msg-identity/src/mutations.rs @@ -1,6 +1,10 @@ //! Typed ports for identity mutations inside one existing metadata transaction. //! There is no arbitrary SQL escape hatch and no commit operation on this port. -use crate::{browser::BrowserStore, models::*}; +use crate::{ + browser::BrowserStore, + models::*, + vault::{VaultRecord, VaultStore}, +}; use msg_core::{Json, Result}; #[derive(Clone)] @@ -36,7 +40,7 @@ pub struct TokenDelivery { pub consumed_at: Option, } -pub trait IdentityStore: BrowserStore { +pub trait IdentityStore: BrowserStore + VaultStore { /// Resolve the caller's reference without changing the signed request. fn resolve_identity(&self, reference: &str) -> Result; /// Compare the persisted auth version; -1 permits only a new identity. @@ -62,6 +66,7 @@ pub trait IdentityStore: BrowserStore { /// Decode and append through the store's hash-chained audit API. fn append_identity_audit(&mut self, audit: &Json) -> Result<()>; fn insert_token_delivery(&mut self, delivery: &TokenDelivery) -> Result<()>; + fn insert_custodial_vault(&mut self, vault: &VaultRecord) -> Result<()>; fn token_delivery(&self, credential: &str) -> Result>; /// Atomically require claimed_at AND consumed_at NULL, then set claimed_at. fn claim_token_delivery(&mut self, credential: &str, now: Timestamp) -> Result; diff --git a/rust/crates/msg-identity/src/vault.rs b/rust/crates/msg-identity/src/vault.rs new file mode 100644 index 00000000..f9633844 --- /dev/null +++ b/rust/crates/msg-identity/src/vault.rs @@ -0,0 +1,243 @@ +//! The existing custodial vault; self-custody accounts never enter this path. +//! The caller must authorize the current operation in the same transaction. +use crate::{models::*, mutations::IdentityKey, require, store::AuthorityStore}; +use hmac::{Hmac, Mac}; +use msg_core::{Error, Result}; +use msg_crypto::{age_recipient, key_id, vault_box, Ed25519Signer}; +use sha2::Sha256; +use zeroize::Zeroizing; + +#[derive(Clone)] +pub struct VaultRecord { + pub subject: String, + pub signing_key_id: String, + pub encryption_key_id: String, + pub signing: Option, + pub encryption: Option, + pub status: String, + pub created_at: Timestamp, + pub destroyed_at: Option, +} + +pub trait VaultStore: AuthorityStore { + fn vault_record(&self, subject: &str) -> Result>; + fn primary_identity_key(&self, subject: &str) -> Result>; +} + +fn key(token_secret: &[u8; 32]) -> Result> { + let mut mac = + Hmac::::new_from_slice(token_secret).map_err(|_| Error("invalid_service_key"))?; + mac.update(b"custodial-vault-aesgcm-v1"); + Ok(Zeroizing::new(mac.finalize().into_bytes().into())) +} +fn aad(subject: &str, key_id: &str, kind: &str) -> Result { + wire(&serde_json::json!({"domain":"msg-custodial-vault-v1","subject":subject,"key_id":key_id,"kind":kind}))?.canonical() +} +fn read(store: &dyn VaultStore, subject: &str, encryption: bool) -> Result { + require( + !store.has_setting("recovery_quarantine")?, + "recovery_quarantined", + )?; + let record = store + .vault_record(subject)? + .ok_or(Error("custodial_vault_unavailable"))?; + require(record.subject == subject, "storage_record_mismatch")?; + require( + record.status == "active" || (encryption && record.status == "decrypt_only"), + "custodial_vault_unavailable", + )?; + Ok(record) +} + +/// Open only the live primary custodial signing key. No private-key export API +/// exists on the returned signer. A caller cannot use this to sign for a +/// self-custody account or resume a key after custody was retired. +pub fn open_signer( + store: &dyn VaultStore, + token_secret: &[u8; 32], + subject: &str, + now: Timestamp, +) -> Result { + let owner = store.subject(subject)?; + require( + owner.kind == SubjectKind::Custodial && !owner.local_only, + "not_custodial", + )?; + let record = read(store, subject, false)?; + let primary = store + .primary_identity_key(subject)? + .ok_or(Error("identity_key_not_found"))?; + require( + primary.key_id == record.signing_key_id && primary.retired_at.is_none(), + "custodial_signing_key_unavailable", + )?; + let credential = store.credential(&record.signing_key_id)?; + require( + credential.subject_id == subject + && credential.kind == CredentialKind::SigningKey + && credential.current_at(now), + "custodial_signing_key_unavailable", + )?; + let sealed = record + .signing + .as_ref() + .ok_or(Error("custodial_vault_unavailable"))?; + let bytes = vault_box::open( + &key(token_secret)?, + aad(subject, &record.signing_key_id, "identity")?.as_bytes(), + sealed, + ) + .map_err(|_| Error("custodial_vault_corrupt"))?; + let seed: &[u8; 32] = bytes + .as_slice() + .try_into() + .map_err(|_| Error("custodial_vault_corrupt"))?; + let signer = Ed25519Signer::from_seed(seed); + require( + key_id(&signer.public_key()) == record.signing_key_id + && signer.public_key().as_slice() == primary.public_key.0.as_slice() + && credential.verifier.0.as_slice() == primary.public_key.0.as_slice(), + "custodial_vault_corrupt", + )?; + Ok(signer) +} + +pub fn open_age_identity( + store: &dyn VaultStore, + token_secret: &[u8; 32], + subject: &str, +) -> Result> { + let record = read(store, subject, true)?; + let sealed = record + .encryption + .as_ref() + .ok_or(Error("custodial_vault_unavailable"))?; + let bytes = vault_box::open( + &key(token_secret)?, + aad(subject, &record.encryption_key_id, "encryption")?.as_bytes(), + sealed, + ) + .map_err(|_| Error("custodial_vault_corrupt"))?; + let identity = Zeroizing::new( + std::str::from_utf8(&bytes) + .map_err(|_| Error("custodial_vault_corrupt"))? + .to_owned(), + ); + let recipient = age_recipient::recipient_from_identity(&identity) + .map_err(|_| Error("custodial_vault_corrupt"))?; + require( + age_recipient::encryption_key_id(&age_recipient::public_from_recipient(&recipient)?) + == record.encryption_key_id, + "custodial_vault_corrupt", + )?; + Ok(identity) +} + +pub fn seal_keys( + token_secret: &[u8; 32], + subject: &str, + signing_seed: &[u8; 32], + age_private: &[u8; 32], + now: Timestamp, +) -> Result { + let signing = Ed25519Signer::from_seed(signing_seed); + let signing_key_id = key_id(&signing.public_key()); + let identity = age_recipient::identity_from_private(age_private); + let recipient = age_recipient::recipient_from_identity(&identity)?; + let encryption_key_id = + age_recipient::encryption_key_id(&age_recipient::public_from_recipient(&recipient)?); + let vault_key = key(token_secret)?; + Ok(VaultRecord { + signing: Some(vault_box::seal( + &vault_key, + aad(subject, &signing_key_id, "identity")?.as_bytes(), + signing_seed, + )?), + encryption: Some(vault_box::seal( + &vault_key, + aad(subject, &encryption_key_id, "encryption")?.as_bytes(), + identity.as_bytes(), + )?), + subject: subject.into(), + signing_key_id, + encryption_key_id, + status: "active".into(), + created_at: now, + destroyed_at: None, + }) +} + +fn upgrade_aad(subject: &str, challenge: &str) -> Result { + wire(&serde_json::json!({"domain":"msg-custodial-upgrade-ephemeral-v1","subject":subject,"challenge_id":challenge}))?.canonical() +} +pub fn seal_upgrade_private( + token_secret: &[u8; 32], + subject: &str, + challenge: &str, + private: &[u8; 32], +) -> Result { + vault_box::seal( + &key(token_secret)?, + upgrade_aad(subject, challenge)?.as_bytes(), + private, + ) +} +pub fn open_upgrade_private( + token_secret: &[u8; 32], + subject: &str, + challenge: &str, + sealed: &vault_box::SealedSecret, +) -> Result> { + let bytes = vault_box::open( + &key(token_secret)?, + upgrade_aad(subject, challenge)?.as_bytes(), + sealed, + ) + .map_err(|_| Error("custodial_upgrade_challenge_corrupt"))?; + let fixed: &[u8; 32] = bytes + .as_slice() + .try_into() + .map_err(|_| Error("custodial_upgrade_challenge_corrupt"))?; + Ok(Zeroizing::new(*fixed)) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn opens_python_vault_ciphertext_with_the_existing_domains() { + let sealed = vault_box::SealedSecret { + nonce: "AAECAwQFBgcICQoL".into(), + ciphertext: "XngSQSqS5S2zJ-2pfPKR_3HBhEbroZupgu6eQhPKYdsXYDDCSRAKYxFTgZoue9qb".into(), + }; + let key_id = "k_b600306cfa76723fdec395e53a9b3d9fdb78b1e2d7a23c32fcbcd2dc6d0c4092"; + let key = key(&[9; 32]).unwrap(); + assert_eq!( + msg_core::b64(key.as_ref()), + "uwtpO4w0Rf0mMYdvOWpXvElt5RQwJCsZ0iCKtEV0lGA" + ); + let plain = vault_box::open( + &key, + aad("u_cust_fixture", key_id, "identity") + .unwrap() + .as_bytes(), + &sealed, + ) + .unwrap(); + assert_eq!(&*plain, &[42; 32]); + assert!(vault_box::open( + &key, + aad("u_other", key_id, "identity").unwrap().as_bytes(), + &sealed + ) + .is_err()); + assert!(vault_box::open( + &key, + aad("u_cust_fixture", key_id, "encryption") + .unwrap() + .as_bytes(), + &sealed + ) + .is_err()); + } +} diff --git a/rust/crates/msg-runtime/Cargo.toml b/rust/crates/msg-runtime/Cargo.toml new file mode 100644 index 00000000..addc5764 --- /dev/null +++ b/rust/crates/msg-runtime/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "msg-runtime" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +publish.workspace = true + +[dependencies] +msg-core = { path = "../msg-core" } +msg-crypto = { path = "../msg-crypto" } +msg-protocol = { path = "../msg-protocol" } +msg-identity = { path = "../msg-identity" } +msg-storage = { path = "../msg-storage" } +msg-worker = { path = "../msg-worker" } +chrono.workspace = true +serde.workspace = true +serde_json.workspace = true +sha2.workspace = true +rand_core.workspace = true +zeroize.workspace = true +hmac.workspace = true +subtle.workspace = true +jsonschema.workspace = true +unicode-normalization.workspace = true +roxmltree.workspace = true +regex.workspace = true +toml.workspace = true +url.workspace = true +rustix.workspace = true +libc.workspace = true + +[lints] +workspace = true diff --git a/rust/crates/msg-runtime/src/catalog.rs b/rust/crates/msg-runtime/src/catalog.rs new file mode 100644 index 00000000..c5c3f990 --- /dev/null +++ b/rust/crates/msg-runtime/src/catalog.rs @@ -0,0 +1,370 @@ +//! Release-owned contracts. No request can register executable code or schemas. +use msg_core::{Error, Json, Result}; +use msg_identity::{ + authentication::OperationPolicy, + authorization::ResourceTypePolicy, + models::{Grant, ResourceRef}, + policy::{CapabilityPolicy, Registry}, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use std::collections::{BTreeMap, BTreeSet}; + +#[derive(Clone, Deserialize, Serialize)] +pub struct ResourceTypeSpec { + pub name: String, + pub version: u32, + pub container: bool, + pub taggable: bool, + pub purchasable: bool, + pub content_schema: Option, + pub relations: BTreeSet, + pub operations: BTreeSet, +} + +#[derive(Clone, Deserialize)] +pub struct AccessSpec { + pub argument: String, + pub check: String, + pub default: Option, +} + +#[derive(Clone, Deserialize, Serialize)] +pub struct OperationSpec { + pub name: String, + pub version: u32, + pub effect: String, + pub entries: BTreeSet, + pub require_signature: bool, + pub anonymous_only: bool, + pub enabled: bool, + pub input_schema: ResourceRef, + pub output_schema: ResourceRef, + pub requires_rules: Vec, + #[serde(default, skip_serializing_if = "std::ops::Not::not")] + pub deprecated: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub replaced_by: Option, + #[serde(default, skip_serializing)] + pub access: Option, + #[serde(skip_serializing)] + pub handler: String, + #[serde(skip_serializing)] + pub requirements: String, +} +impl OperationSpec { + pub fn id(&self) -> String { + format!("{}@{}", self.name, self.version) + } + pub fn policy(&self) -> OperationPolicy { + OperationPolicy { + name: self.name.clone(), + version: self.version, + effect: self.effect.clone(), + entries: self.entries.clone(), + require_signature: self.require_signature, + anonymous_only: self.anonymous_only, + } + } + pub fn describe(&self) -> Result { + Json::parse(&serde_json::to_string(self).map_err(|_| Error("invalid_registry"))?) + } +} + +#[derive(Deserialize)] +struct Manifest { + dependencies: Vec, + operations: Vec, +} +#[derive(Deserialize)] +struct Reference { + version: u32, + operations: Vec, + schemas: BTreeMap, + capabilities: Vec, + resource_types: Vec, + base_grants: Vec, + primary_ceiling: Vec, + temporary_ceiling: Vec, + base_families: BTreeSet, + plugins: Vec, + #[serde(default)] + plugin_manifests: BTreeMap, +} + +pub struct Catalog { + operations: BTreeMap<(String, u32), OperationSpec>, + resource_types: BTreeMap<(String, u32), ResourceTypeSpec>, + schemas: BTreeMap, + validators: BTreeMap, + capabilities: Vec, + published_capabilities: Vec, + pub base_grants: Vec, + pub primary_ceiling: Vec, + pub temporary_ceiling: Vec, + pub base_families: BTreeSet, +} +impl Catalog { + pub fn builtins() -> Result { + Self::load(None) + } + + pub fn load(enabled: Option<&[String]>) -> Result { + let mut reference: Reference = + serde_json::from_str(include_str!("../../../assets/catalog.json")) + .map_err(|_| Error("invalid_registry"))?; + if reference.version != 1 { + return Err(Error("invalid_registry")); + } + if let Some(enabled) = enabled { + let selected: BTreeSet<_> = enabled.iter().cloned().collect(); + if selected.len() != enabled.len() + || !selected.contains("identity") + || !selected.iter().all(|x| reference.plugins.contains(x)) + { + return Err(Error("invalid_plugin_list")); + } + if selected.len() != reference.plugins.len() { + if reference.plugin_manifests.is_empty() { + return Err(Error("invalid_registry")); + } + let mut allowed = BTreeSet::new(); + for (name, manifest) in &reference.plugin_manifests { + let active = selected.contains(name) + || (name == "file" + && ["content", "discovery", "batch"] + .iter() + .all(|p| selected.contains(*p))) + || (name == "game" && selected.contains("communication")); + if active { + if !manifest.dependencies.iter().all(|p| selected.contains(p)) { + return Err(Error("missing_plugin_dependency")); + } + allowed.extend(manifest.operations.iter().cloned()); + } + } + reference.operations.retain(|s| allowed.contains(&s.id())); + for grants in [ + &mut reference.base_grants, + &mut reference.primary_ceiling, + &mut reference.temporary_ceiling, + ] { + for grant in grants.iter_mut() { + grant.operations.retain(|op| allowed.contains(op)); + } + grants.retain(|g| !g.operations.is_empty()); + } + } + } + let mut operations = BTreeMap::new(); + for spec in reference.operations { + if spec.version == 0 + || spec.name.is_empty() + || spec.name.contains('*') + || !matches!(spec.effect.as_str(), "read" | "transaction" | "external") + || spec.entries.is_empty() + || !spec + .entries + .iter() + .all(|x| matches!(x.as_str(), "network" | "worker" | "local_admin")) + || (spec.name.starts_with("root.") + && spec.entries != BTreeSet::from(["local_admin".to_owned()])) + || !reference.schemas.contains_key(&spec.input_schema.id) + || !reference.schemas.contains_key(&spec.output_schema.id) + { + return Err(Error("invalid_registry")); + } + if operations + .insert((spec.name.clone(), spec.version), spec) + .is_some() + { + return Err(Error("duplicate_operation")); + } + } + let mut resource_types = BTreeMap::new(); + for spec in reference.resource_types { + if resource_types + .insert((spec.name.clone(), spec.version), spec) + .is_some() + { + return Err(Error("duplicate_resource_type")); + } + } + let mut validators = BTreeMap::new(); + for (name, schema) in &reference.schemas { + local_schema(schema)?; + let validator = jsonschema::options() + .with_draft(jsonschema::Draft::Draft202012) + .should_validate_formats(false) + .build(schema) + .map_err(|_| Error("invalid_schema"))?; + validators.insert(name.clone(), validator); + } + let capabilities = reference + .capabilities + .iter() + .cloned() + .map(serde_json::from_value) + .collect::, _>>() + .map_err(|_| Error("invalid_registry"))?; + Ok(Self { + operations, + resource_types, + schemas: reference.schemas, + validators, + capabilities, + published_capabilities: reference.capabilities, + base_grants: reference.base_grants, + primary_ceiling: reference.primary_ceiling, + temporary_ceiling: reference.temporary_ceiling, + base_families: reference.base_families, + }) + } + pub fn operation(&self, name: &str, version: u32) -> Result<&OperationSpec> { + self.operations + .get(&(name.to_owned(), version)) + .ok_or(Error("unknown_operation")) + } + pub fn operations(&self) -> impl Iterator { + self.operations.values() + } + pub fn policies(&self) -> Vec { + self.operations().map(OperationSpec::policy).collect() + } + pub fn resource_type(&self, name: &str, version: u32) -> Result<&ResourceTypeSpec> { + self.resource_types + .get(&(name.to_owned(), version)) + .ok_or(Error("unknown_resource_type")) + } + pub fn resource_types(&self) -> impl Iterator { + self.resource_types.values() + } + pub fn resource_policies(&self) -> Vec { + self.resource_types() + .map(|s| ResourceTypePolicy { + name: s.name.clone(), + version: s.version, + container: s.container, + }) + .collect() + } + pub fn capability_registry(&self) -> Result { + Registry::new(self.capabilities.clone()) + } + pub fn capabilities(&self) -> Result { + Ok(Value::Array(self.published_capabilities.clone())) + } + pub fn catalog(&self) -> Result { + let operations = self + .operations() + .filter(|s| s.entries.contains("network")) + .map(|s| serde_json::to_value(s).map_err(|_| Error("invalid_registry"))) + .collect::>>()?; + let digest = msg_core::digest(&Json::parse( + &serde_json::to_string(&operations).map_err(|_| Error("invalid_registry"))?, + )?)?; + Ok(serde_json::json!({"version":1,"digest":digest,"operations":operations})) + } + pub fn schema(&self, id: &str) -> Result<&Value> { + self.schemas.get(id).ok_or(Error("schema_not_found")) + } + pub fn validate(&self, id: &str, instance: &Json) -> Result<()> { + let value: Value = + serde_json::from_str(&instance.canonical()?).map_err(|_| Error("invalid_json"))?; + if !self + .validators + .get(id) + .ok_or(Error("schema_not_found"))? + .is_valid(&value) + { + return Err(Error("schema_validation")); + } + Ok(()) + } +} + +// Only schema-bearing keywords are traversed: an instance property named "$ref" +// or text inside examples/defaults is data, not a schema to resolve. +pub fn local_schema(schema: &Value) -> Result<()> { + let Some(object) = schema.as_object() else { + return Ok(()); + }; + for key in ["$ref", "$dynamicRef", "$recursiveRef"] { + if let Some(value) = object.get(key) { + if !value + .as_str() + .is_some_and(|v| v.is_empty() || v.starts_with('#')) + { + return Err(Error("remote_schema_reference_forbidden")); + } + } + } + for key in [ + "$defs", + "definitions", + "properties", + "patternProperties", + "dependentSchemas", + ] { + if let Some(values) = object.get(key).and_then(Value::as_object) { + for value in values.values() { + local_schema(value)?; + } + } + } + for key in ["allOf", "anyOf", "oneOf", "prefixItems"] { + if let Some(values) = object.get(key).and_then(Value::as_array) { + for value in values { + local_schema(value)?; + } + } + } + for key in [ + "not", + "if", + "then", + "else", + "items", + "contains", + "additionalProperties", + "unevaluatedProperties", + "unevaluatedItems", + "propertyNames", + "contentSchema", + ] { + if let Some(value) = object.get(key) { + local_schema(value)?; + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + #[test] + fn contract_reference_is_self_contained() { + let c = Catalog::builtins().unwrap(); + assert!(c.operations().count() >= 444); + assert_eq!( + c.operation("identity.temporary", 3).unwrap().effect, + "transaction" + ); + assert!(c.operation("caller.install", 1).is_err()); + assert!(c + .validate("schema:identity.register:2", &Json::parse("{}").unwrap()) + .is_err()); + } + #[test] + fn remote_schemas_cannot_read_files_or_make_requests() { + for uri in [ + "file:///etc/passwd", + "http://localhost/private", + "schema:other", + ] { + assert!(local_schema(&json!({"allOf":[{"$ref":uri}]})).is_err()); + } + assert!(local_schema(&json!({"properties":{"$ref":{"type":"string"}},"default":{"$ref":"https://example.org"}})).is_ok()); + } +} diff --git a/rust/crates/msg-runtime/src/config.rs b/rust/crates/msg-runtime/src/config.rs new file mode 100644 index 00000000..3911e7d8 --- /dev/null +++ b/rust/crates/msg-runtime/src/config.rs @@ -0,0 +1,883 @@ +//! Read the installed msgd configuration and public trust. Never initialize keys. +use msg_core::{unb64, Error, Json, Result}; +use msg_crypto::{age_recipient, Ed25519Signer}; +use msg_identity::{ + browser, + models::{decode, Certificate}, +}; +use msg_worker::mail::MailConfig; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{ + collections::BTreeSet, + fs::{self, File, OpenOptions}, + io::Read, + path::{Component, Path, PathBuf}, +}; +use url::Url; +use zeroize::Zeroizing; + +pub const BUILTINS: &[&str] = &[ + "identity", + "content", + "discussion", + "communication", + "discovery", + "achievements", + "recovery", + "sharing", + "money", + "offers", + "store", + "bounty", + "orders", + "delivery", + "transfer", + "extensions", + "system", + "batch", +]; + +#[derive(Clone, Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +pub struct RecoveryCustodian { + pub id: String, + pub name: String, + pub recipient: String, + #[serde(default)] + pub description: Option, + #[serde(default)] + pub policy_ref: Option, + #[serde(skip_deserializing)] + pub fingerprint: String, +} +pub struct Settings { + pub config_dir: PathBuf, + pub service_url: String, + pub service_aliases: Vec, + pub listen: String, + pub port: u16, + pub postgres_dsn: String, + pub valkey_url: Option, + pub content_dir: PathBuf, + pub repositories_dir: PathBuf, + pub blob_dir: PathBuf, + pub staging_dir: PathBuf, + pub service_keys_dir: PathBuf, + pub root_private_dir: PathBuf, + pub recovery_marker: PathBuf, + pub plugins: Vec, + pub max_request_bytes: usize, + pub max_response_bytes: usize, + pub max_path_bytes: usize, + pub max_part_bytes: usize, + pub temporary_ttl: u64, + pub transfer_ttl: u64, + pub base_certificate_ttl: u64, + pub credential_delivery_recovery_window: u64, + pub handle_rename_enabled: bool, + pub browser: browser::Config, + pub mail: Option, + pub websub_hubs: Vec, + pub recovery_custodians: Vec, + pub public_web_origin: Option, + pub hosting_base_capacity_bytes: u64, + pub hosting_content_group_read: bool, + pub hosting_recovery_marker: Option, + pub money_display_name: String, + pub money_code: String, + pub tool_timeout_ms: u64, + pub tool_max_response_bytes: usize, + pub tool_methods: Vec, + pub tool_ports: Vec, + pub token_secret: Zeroizing<[u8; 32]>, + pub online_signer: Ed25519Signer, + pub receipt_signer: Ed25519Signer, + pub root_certificate: Certificate, + pub root_public_key: [u8; 32], +} + +struct Section(toml::Table); +impl Section { + fn get(&self, key: &str, default: T) -> Result { + self.0 + .get(key) + .map(|v| { + v.clone() + .try_into() + .map_err(|_| Error("invalid_configuration")) + }) + .unwrap_or(Ok(default)) + } + fn optional(&self, key: &str) -> Result> { + self.0 + .get(key) + .map(|v| { + v.clone() + .try_into() + .map_err(|_| Error("invalid_configuration")) + }) + .transpose() + } + fn required(&self, key: &str, code: &'static str) -> Result { + self.optional(key)?.ok_or(Error(code)) + } +} +fn section(root: &toml::Table, name: &str, keys: &[&str], error: &'static str) -> Result
{ + let value = match root.get(name) { + Some(toml::Value::Table(table)) => table.clone(), + None => toml::Table::new(), + _ => return Err(Error(error)), + }; + require(value.keys().all(|k| keys.contains(&k.as_str())), error)?; + Ok(Section(value)) +} +fn require(value: bool, code: &'static str) -> Result<()> { + if value { + Ok(()) + } else { + Err(Error(code)) + } +} +fn exists(path: &Path) -> bool { + fs::symlink_metadata(path).is_ok() +} +fn read(path: &Path, maximum: u64, private: bool) -> Result>> { + #[cfg(unix)] + use std::os::unix::fs::{MetadataExt, OpenOptionsExt}; + let mut options = OpenOptions::new(); + options.read(true); + #[cfg(unix)] + options.custom_flags(libc::O_NOFOLLOW | libc::O_CLOEXEC | libc::O_NONBLOCK); + let mut file: File = options + .open(path) + .map_err(|_| Error("configuration_missing"))?; + let meta = file + .metadata() + .map_err(|_| Error("configuration_missing"))?; + require( + meta.is_file() && meta.len() <= maximum, + "unsafe_configuration_file", + )?; + #[cfg(unix)] + if private { + require( + meta.mode() & 0o007 == 0 && meta.mode() & 0o022 == 0, + "unsafe_service_key", + )?; + } + let mut data = Zeroizing::new(Vec::new()); + file.by_ref() + .take(maximum + 1) + .read_to_end(&mut data) + .map_err(|_| Error("configuration_missing"))?; + require(data.len() as u64 <= maximum, "unsafe_configuration_file")?; + Ok(data) +} +fn key(directory: &Path, name: &str) -> Result> { + let value = read(&directory.join(name), 32, true)?; + Ok(Zeroizing::new( + value + .as_slice() + .try_into() + .map_err(|_| Error("invalid_service_key"))?, + )) +} +fn path(value: PathBuf) -> Result { + require( + value.is_absolute() + && !value.components().any(|c| c == Component::ParentDir) + && !value.to_string_lossy().chars().any(char::is_control), + "storage_paths_must_be_absolute", + )?; + Ok(value) +} +fn absolute(value: &Path) -> Result { + path(if value.is_absolute() { + value.to_path_buf() + } else { + std::env::current_dir() + .map_err(|_| Error("configuration_missing"))? + .join(value) + }) +} +pub fn service_origin(value: &str) -> Result { + require( + !value.is_empty() + && !value + .chars() + .any(|c| c <= ' ' || c == '\u{7f}' || c == '\\'), + "invalid_server_url", + )?; + let mut url = + Url::parse(value.trim_end_matches('/')).map_err(|_| Error("invalid_server_url"))?; + require( + matches!(url.scheme(), "https" | "http") + && url.host_str().is_some() + && url.username().is_empty() + && url.password().is_none() + && (url.path().is_empty() || url.path() == "/") + && url.query().is_none() + && url.fragment().is_none() + && url.port() != Some(0), + "invalid_server_url", + )?; + if let Some(url::Host::Domain(domain)) = url.host() { + let name = domain.trim_end_matches('.').to_owned(); + require( + name.len() <= 253 + && name.split('.').all(|part| { + !part.is_empty() + && part.len() <= 63 + && part.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-') + && !part.starts_with('-') + && !part.ends_with('-') + }), + "invalid_server_url", + )?; + url.set_host(Some(&name)) + .map_err(|_| Error("invalid_server_url"))?; + } + Ok(url.origin().ascii_serialization()) +} +fn protected_root(directory: &Path) -> Result { + if directory == Path::new("/etc/msgd") { + return Ok(PathBuf::from("/var/lib/msgd-root")); + } + let name = directory + .file_name() + .and_then(|v| v.to_str()) + .ok_or(Error("invalid_configuration"))?; + let parent = directory.parent().ok_or(Error("invalid_configuration"))?; + let legacy = parent.join(format!("{name}-root")); + if parent != Path::new("/etc/msgd") { + return Ok(legacy); + } + let current = PathBuf::from("/var/lib/private/msgd") + .join(name) + .join("root"); + let has_state = |p: &Path| { + [ + "key.json", + "initialization.pending", + "rotation.pending.json", + "history", + ".rotation.lock", + ] + .iter() + .any(|n| exists(&p.join(n))) + }; + let old = has_state(&legacy); + let new = has_state(¤t); + require(!(old && new), "ambiguous_root_private_directory")?; + if old { + return Ok(legacy); + } + validate_instance(name)?; + Ok(current) +} +pub fn validate_instance(name: &str) -> Result<()> { + require( + !name.is_empty() + && name.len() <= 26 + && name.as_bytes()[0].is_ascii_lowercase() + && name + .bytes() + .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || b"_-".contains(&c)), + "invalid_instance_name", + ) +} + +impl Settings { + pub fn load(directory: &Path) -> Result { + let directory = absolute(directory)?; + let plugins_dir = directory.join("plugins.d"); + if let Ok(meta) = fs::symlink_metadata(&plugins_dir) { + require( + meta.is_dir() && !meta.file_type().is_symlink(), + "unsafe_plugins_directory", + )?; + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + require( + meta.permissions().mode() & 0o022 == 0, + "unsafe_plugins_directory", + )?; + } + } + let current = directory.join("msgd.toml"); + let legacy = directory.join("server.toml"); + require( + !(exists(¤t) && exists(&legacy)), + "ambiguous_server_configuration", + )?; + let raw = read( + if exists(&legacy) { &legacy } else { ¤t }, + 1_048_576, + false, + )?; + let root: toml::Table = + toml::from_str(std::str::from_utf8(&raw).map_err(|_| Error("invalid_configuration"))?) + .map_err(|_| Error("invalid_configuration"))?; + require( + root.keys().all(|k| { + [ + "server", "storage", "limits", "identity", "tools", "money", "hosting", + "recovery", "plugins", "websub", "browser", + ] + .contains(&k.as_str()) + }), + "unknown_configuration_section", + )?; + let server = section( + &root, + "server", + &[ + "service_url", + "service_aliases", + "listen", + "port", + "public_web_origin", + "temporary_ttl", + "transfer_ttl", + ], + "unknown_server_configuration", + )?; + let storage = section( + &root, + "storage", + &[ + "postgres_dsn", + "valkey_url", + "content", + "repositories", + "blobs", + "staging", + "service_keys", + ], + "unknown_storage_configuration", + )?; + let limits = section( + &root, + "limits", + &[ + "request_bytes", + "response_bytes", + "path_bytes", + "part_bytes", + ], + "unknown_limit", + )?; + let identity = section( + &root, + "identity", + &[ + "credential_delivery_recovery_window", + "handle_rename_enabled", + ], + "unknown_identity_configuration", + )?; + let tools = section( + &root, + "tools", + &[ + "isolation", + "timeout_ms", + "max_response_bytes", + "methods", + "ports", + ], + "unknown_tool_configuration", + )?; + let money = section( + &root, + "money", + &[ + "enabled", + "currency_id", + "display_name", + "code", + "scale", + "transfer_fee", + "allow_overdraft", + ], + "unknown_money_configuration", + )?; + let hosting = section( + &root, + "hosting", + &[ + "base_capacity_bytes", + "content_group_read", + "recovery_marker", + ], + "unknown_hosting_configuration", + )?; + let recovery = section( + &root, + "recovery", + &["custodians"], + "unknown_recovery_configuration", + )?; + let plugins = section( + &root, + "plugins", + &["enabled"], + "unknown_plugin_configuration", + )?; + let websub = section(&root, "websub", &["hubs"], "unknown_websub_configuration")?; + let browser = section( + &root, + "browser", + &["enabled", "session_ttl"], + "invalid_browser_config", + )?; + let service_url: String = server.required("service_url", "invalid_service_url")?; + let canonical = service_origin(&service_url).map_err(|_| Error("invalid_service_url"))?; + // Target service strings retain the installed spelling, as Python does. + let service_url = service_url.trim_end_matches('/').to_owned(); + let aliases: Vec = server.get("service_aliases", Vec::new())?; + require(aliases.len() <= 32, "invalid_service_aliases")?; + let service_aliases = aliases + .iter() + .map(|s| service_origin(s)) + .collect::>>()?; + let unique: BTreeSet<_> = service_aliases.iter().collect(); + require( + unique.len() == service_aliases.len() + && !unique.contains(&canonical) + && service_aliases + .iter() + .all(|s| s.split(':').next() == service_url.split(':').next()), + "invalid_service_aliases", + )?; + let listen: String = server.get("listen", "127.0.0.1".into())?; + require(!listen.is_empty(), "invalid_listen_address")?; + let port: u16 = server.get("port", 8042)?; + require(port > 0, "invalid_listen_port")?; + let temporary_ttl = server.get("temporary_ttl", 3600_u64)?; + let transfer_ttl = server.get("transfer_ttl", 86400_u64)?; + require(temporary_ttl > 0 && transfer_ttl > 0, "invalid_ttl")?; + let postgres_dsn: String = storage.required("postgres_dsn", "invalid_postgres_dsn")?; + require( + !postgres_dsn.trim().is_empty() && !postgres_dsn.chars().any(char::is_control), + "invalid_postgres_dsn", + )?; + if postgres_dsn.starts_with("service=") { + require( + postgres_dsn.len() > 8 && !postgres_dsn.chars().any(char::is_whitespace), + "invalid_postgres_dsn", + )?; + } else { + let dsn = Url::parse(&postgres_dsn).map_err(|_| Error("invalid_postgres_dsn"))?; + require( + matches!(dsn.scheme(), "postgres" | "postgresql") + && dsn.host_str().is_some() + && dsn.fragment().is_none(), + "invalid_postgres_dsn", + )?; + } + let valkey_url: Option = storage.optional("valkey_url")?; + if let Some(value) = &valkey_url { + let value = Url::parse(value).map_err(|_| Error("invalid_valkey_url"))?; + require( + matches!(value.scheme(), "redis" | "rediss" | "unix") + && value.fragment().is_none() + && if value.scheme() == "unix" { + !value.path().is_empty() + } else { + value.host_str().is_some() + }, + "invalid_valkey_url", + )?; + } + let content_dir = + path(storage.get("content", PathBuf::from("/var/lib/msgd/git/content"))?)?; + let parent = content_dir + .parent() + .ok_or(Error("storage_paths_must_be_absolute"))?; + let modern = content_dir.file_name().is_some_and(|n| n == "content") + && parent.file_name().is_some_and(|n| n == "git"); + let data_dir = if modern { + parent + .parent() + .ok_or(Error("storage_paths_must_be_absolute"))? + } else { + parent + }; + let repositories_dir = path(storage.get( + "repositories", + parent.join(if modern { "repos" } else { "repositories" }), + )?)?; + let blob_dir = path(storage.get( + "blobs", + if modern { + data_dir.join("blobs/sha256") + } else { + content_dir.join("binary") + }, + )?)?; + let service_keys_dir = path(storage.get( + "service_keys", + if !modern && exists(&directory.join("service")) { + directory.join("service") + } else { + data_dir.join("service") + }, + )?)?; + let staging_dir = path(storage.get("staging", data_dir.join("transfers/staging"))?)?; + let limit = |name, default| -> Result { + let value = limits.get(name, default)?; + require(value >= 256, "invalid_limit")?; + Ok(value) + }; + let max_request_bytes = limit("request_bytes", 1_048_576)?; + let max_response_bytes = limit("response_bytes", 1_048_576)?; + let max_path_bytes = limit("path_bytes", 8192)?; + let max_part_bytes = limit("part_bytes", 65536)?; + let window: String = identity.get("credential_delivery_recovery_window", "15m".into())?; + let minutes = window + .strip_suffix('m') + .and_then(|s| { + if !s.starts_with('0') { + s.parse::().ok() + } else { + None + } + }) + .ok_or(Error("invalid_credential_delivery_recovery_window"))?; + require( + window.len() <= 3 && (1..=60).contains(&minutes), + "invalid_credential_delivery_recovery_window", + )?; + let enabled = browser.get("enabled", true)?; + let session_ttl = browser.get("session_ttl", 3600_u32)?; + require( + (60..=86400).contains(&session_ttl), + "invalid_browser_config", + )?; + let origin = Url::parse(&canonical).map_err(|_| Error("invalid_service_url"))?; + require( + !enabled + || origin.scheme() == "https" + || matches!( + origin.host_str(), + Some("localhost" | "127.0.0.1" | "[::1]" | "testserver") + ), + "secure_channel_required", + )?; + let public_web_origin: Option = server.optional("public_web_origin")?; + if let Some(value) = &public_web_origin { + service_origin(value).map_err(|_| Error("invalid_hosting_origin"))?; + require( + value.trim_end_matches('/') == service_url, + "hosting_origin_must_match_service", + )?; + } + let hosting_base_capacity_bytes = hosting.get("base_capacity_bytes", 10_485_760_u64)?; + require( + (1..=1_099_511_627_776).contains(&hosting_base_capacity_bytes), + "invalid_hosting_capacity", + )?; + let hosting_recovery_marker = hosting + .optional::("recovery_marker")? + .map(path) + .transpose()?; + require( + money.get("enabled", true)? + && money.get("currency_id", "primary".to_owned())? == "primary" + && money.get("scale", 6_i64)? == 6 + && money.get("transfer_fee", 0_i64)? == 0 + && !money.get("allow_overdraft", false)?, + "invalid_money_configuration", + )?; + let money_display_name: String = money.get("display_name", "MSG".into())?; + let money_code: String = money.get("code", "MSG".into())?; + require( + (1..=80).contains(&money_display_name.chars().count()) + && money_display_name.trim() == money_display_name + && !money_display_name.chars().any(|c| c < ' '), + "invalid_money_display_name", + )?; + require( + (2..=16).contains(&money_code.len()) + && money_code.as_bytes()[0].is_ascii_uppercase() + && money_code + .bytes() + .all(|c| c.is_ascii_uppercase() || c.is_ascii_digit()), + "invalid_money_code", + )?; + require( + tools.get("isolation", "bubblewrap".to_owned())? == "bubblewrap", + "unsafe_tool_worker", + )?; + let tool_timeout_ms = tools.get("timeout_ms", 10000_u64)?; + let tool_max_response_bytes = tools.get("max_response_bytes", 4194304_usize)?; + require( + tool_timeout_ms > 0 && tool_max_response_bytes > 0, + "invalid_tool_limit", + )?; + let tool_methods: Vec = tools.get("methods", vec!["GET".into(), "HEAD".into()])?; + require( + tool_methods.iter().all(|s| { + ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"].contains(&s.as_str()) + }), + "invalid_tool_methods", + )?; + let tool_ports: Vec = tools.get("ports", vec![80, 443])?; + require(tool_ports.iter().all(|p| *p > 0), "invalid_tool_ports")?; + let plugins: Vec = + plugins.get("enabled", BUILTINS.iter().map(|s| (*s).into()).collect())?; + require( + plugins.iter().collect::>().len() == plugins.len() + && plugins.iter().any(|s| s == "identity") + && plugins.iter().all(|s| BUILTINS.contains(&s.as_str())), + "invalid_plugin_list", + )?; + let websub_hubs: Vec = websub.get("hubs", Vec::new())?; + require(websub_hubs.len() <= 16, "invalid_websub_hubs")?; + for value in &websub_hubs { + msg_worker::outbound::Endpoint::parse(value)?; + } + let mut seen = BTreeSet::new(); + let websub_hubs = websub_hubs + .into_iter() + .filter(|s| seen.insert(s.clone())) + .collect(); + let mut recovery_custodians: Vec = + recovery.get("custodians", Vec::new())?; + require( + recovery_custodians.len() <= 16, + "invalid_recovery_custodians", + )?; + let mut ids = BTreeSet::new(); + for c in &mut recovery_custodians { + require( + (2..=64).contains(&c.id.len()) + && c.id.as_bytes()[0].is_ascii_lowercase() + && c.id + .bytes() + .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'-') + && (1..=100).contains(&c.name.chars().count()) + && [&c.description, &c.policy_ref] + .iter() + .all(|s| s.as_ref().is_none_or(|s| s.chars().count() <= 500)), + "invalid_recovery_custodian", + )?; + require(ids.insert(c.id.clone()), "duplicate_recovery_custodian")?; + c.fingerprint = age_recipient::encryption_key_id( + &age_recipient::public_from_recipient(&c.recipient)?, + ); + } + let mail_file = directory.join("mail.toml"); + let mail = if exists(&mail_file) { + let bytes = read(&mail_file, 65536, false)?; + let table: toml::Table = toml::from_str( + std::str::from_utf8(&bytes).map_err(|_| Error("invalid_mail_configuration"))?, + ) + .map_err(|_| Error("invalid_mail_configuration"))?; + require( + table.keys().all(|s| { + [ + "enabled", + "host", + "port", + "sender", + "tls", + "credential_file", + ] + .contains(&s.as_str()) + }), + "unknown_mail_configuration", + )?; + let section = Section(table); + if !section.get("enabled", false)? { + None + } else { + let host: String = section.required("host", "invalid_mail_host")?; + let sender: String = section.required("sender", "invalid_mail_sender")?; + let tls: String = section.required("tls", "mail_tls_required")?; + let port = section.get("port", 587_u16)?; + require( + !host.is_empty() && !host.chars().any(|c| c <= ' '), + "invalid_mail_host", + )?; + require( + !sender.trim().is_empty() && !sender.chars().any(|c| c < ' '), + "invalid_mail_sender", + )?; + require( + matches!(tls.as_str(), "tls" | "starttls") && port > 0, + "mail_tls_required", + )?; + Some(MailConfig { + enabled: true, + host, + port, + tls, + sender, + credential_file: section + .optional::("credential_file")? + .map(path) + .transpose()?, + }) + } + } else { + None + }; + let current = data_dir.join("recovery-drill.json"); + let legacy = directory.join("recovery-drill.json"); + require( + !(exists(¤t) && exists(&legacy)), + "ambiguous_recovery_marker", + )?; + let recovery_marker = if exists(&legacy) { legacy } else { current }; + let root_private_dir = protected_root(&directory)?; + let trust_dir = directory.join("trust"); + require( + !fs::symlink_metadata(&trust_dir) + .map_err(|_| Error("root_trust_missing"))? + .file_type() + .is_symlink(), + "unsafe_root_trust_path", + )?; + let mut trust: Option = None; + for name in ["root.crt", "root.json"] { + let file = trust_dir.join(name); + if !exists(&file) { + continue; + } + let bytes = read(&file, 1_048_576, false)?; + let value = + Json::parse(std::str::from_utf8(&bytes).map_err(|_| Error("invalid_root_trust"))?)?; + if let Some(previous) = &trust { + require( + previous.canonical()? == value.canonical()?, + "root_trust_alias_mismatch", + )?; + } + trust = Some(value); + } + let trust = trust.ok_or(Error("root_trust_missing"))?; + let object = trust.as_object()?; + require( + object.len() == 3 + && object.get("version").and_then(|v| v.as_integer().ok()) == Some("1"), + "invalid_root_trust", + )?; + let root_public_key = unb64( + object + .get("public_key") + .ok_or(Error("invalid_root_trust"))? + .as_str()?, + 32, + )? + .try_into() + .map_err(|_| Error("invalid_root_trust"))?; + let root_certificate = decode( + object + .get("certificate") + .ok_or(Error("invalid_root_trust"))?, + )?; + let token_secret = key(&service_keys_dir, "tokens.key")?; + let online_signer = Ed25519Signer::from_seed(&key(&service_keys_dir, "online.key")?); + let receipt_signer = Ed25519Signer::from_seed(&key(&service_keys_dir, "receipt.key")?); + Ok(Self { + config_dir: directory, + service_url, + service_aliases, + listen, + port, + postgres_dsn, + valkey_url, + content_dir, + repositories_dir, + blob_dir, + staging_dir, + service_keys_dir, + root_private_dir, + recovery_marker, + plugins, + max_request_bytes, + max_response_bytes, + max_path_bytes, + max_part_bytes, + temporary_ttl, + transfer_ttl, + base_certificate_ttl: 2592000, + credential_delivery_recovery_window: minutes * 60, + handle_rename_enabled: identity.get("handle_rename_enabled", true)?, + browser: browser::Config { + enabled, + session_ttl, + }, + mail, + websub_hubs, + recovery_custodians, + public_web_origin, + hosting_base_capacity_bytes, + hosting_content_group_read: hosting.get("content_group_read", false)?, + hosting_recovery_marker, + money_display_name, + money_code, + tool_timeout_ms, + tool_max_response_bytes, + tool_methods, + tool_ports, + token_secret, + online_signer, + receipt_signer, + root_certificate, + root_public_key, + }) + } + pub fn quarantined(&self) -> bool { + exists(&self.recovery_marker) + } + pub fn require_network_runtime(&self) -> Result<()> { + require( + !rustix::process::geteuid().is_root(), + "network_service_must_not_run_as_root", + )?; + for directory in [&self.root_private_dir, &self.config_dir.join("root")] { + use rustix::fs::{access, Access}; + require( + access(directory, Access::READ_OK).is_err() + && access(directory, Access::EXEC_OK).is_err(), + "root_material_accessible_to_service", + )?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn origins_and_instances_cannot_escape_their_boundaries() { + assert_eq!( + service_origin("HTTPS://Example.COM:443/").unwrap(), + "https://example.com" + ); + assert_eq!( + service_origin("http://[::1]:8042").unwrap(), + "http://[::1]:8042" + ); + for s in [ + "https://x@host", + "https://host/a", + "https://host?x", + "https://host#x", + "https://host\\x", + " https://host", + "https://host:0", + ] { + assert!(service_origin(s).is_err(), "{s}"); + } + for name in ["..", "/root", "a/b", "UPPER", "a b"] { + assert!(validate_instance(name).is_err()); + } + assert!(validate_instance("my-server_2").is_ok()); + } +} diff --git a/rust/crates/msg-runtime/src/content.rs b/rust/crates/msg-runtime/src/content.rs new file mode 100644 index 00000000..b771438d --- /dev/null +++ b/rust/crates/msg-runtime/src/content.rs @@ -0,0 +1,1515 @@ +//! Resource and immutable Revision handlers. The executor owns authorization, +//! request replay and the durable metadata/content transaction. +use crate::context::{new_id, output, Context, HandlerOutput}; +use msg_core::{digest, unb64, Error, Json, Result}; +use msg_crypto::{verify, Signature}; +use msg_identity::{ + authorization::{AccessRequirement, Check}, + models::{CredentialKind, ResourceRef, SubjectKind, Timestamp}, + store::{truthy, AuthorityStore}, +}; +use msg_storage::{ + postgres::Value, + records::{ + decode, encode, BlobRef, Event, Relation, Resource, ResourceState, Revision, + SignatureSource, SourceKind, + }, +}; +use serde_json::{json, Value as J}; +use std::{ + collections::{BTreeMap, BTreeSet}, + io::{self, Write}, +}; +use unicode_normalization::UnicodeNormalization; + +const ROOT: &str = "u_root"; +const ROOT_SPACE: &str = "r_root"; +const SETGID: u16 = 0o2000; +const STICKY: u16 = 0o1000; + +pub fn supports(op: &str, version: u32) -> bool { + match op { + "content.post_create" + | "content.post_write" + | "content.post_edit" + | "content.topic_configure" => (1..=2).contains(&version), + "content.topic_create" + | "content.tags_set" + | "content.post_edit_metadata" + | "content.post_rollback" + | "content.file_put" + | "content.attach" + | "content.template_put" => version == 1, + _ => false, + } +} + +pub fn requirements(ctx: &Context<'_>) -> Result> { + let op = ctx.request.field("operation")?.as_str()?; + let (argument, check) = match op { + "content.topic_create" + | "content.post_create" + | "content.file_put" + | "content.template_put" => ("parent", Check::Create), + "content.post_write" + | "content.post_edit" + | "content.post_edit_metadata" + | "content.post_rollback" => ("id", Check::Write), + "content.topic_configure" => ("id", Check::Manage), + _ => return Ok(vec![]), + }; + Ok(vec![AccessRequirement { + resource_id: ctx.resolve(ctx.text(argument)?, false)?, + operation: ctx.operation()?, + check, + }]) +} + +pub fn handle(ctx: &mut Context<'_>) -> Result { + match ctx.request.field("operation")?.as_str()? { + "content.topic_create" => topic_create(ctx), + "content.post_create" => post_create(ctx), + "content.post_write" | "content.post_edit" => post_edit(ctx), + "content.tags_set" => tags_set(ctx), + "content.post_edit_metadata" => post_metadata(ctx), + "content.post_rollback" => post_rollback(ctx), + "content.file_put" => file_put(ctx), + "content.attach" => attach(ctx), + "content.template_put" => template_put(ctx), + "content.topic_configure" => topic_configure(ctx), + _ => Err(Error("unknown_operation")), + } +} + +fn require(ok: bool, code: &'static str) -> Result<()> { + if ok { + Ok(()) + } else { + Err(Error(code)) + } +} +fn argument(ctx: &Context<'_>, key: &str) -> Result> { + ctx.args()? + .get(key) + .map(|v| v.as_str().map(str::to_owned)) + .transpose() +} +fn reference(resource: &Resource) -> ResourceRef { + ResourceRef { + id: resource.id.clone(), + revision: resource.revision.clone(), + } +} +fn result(resource: &Resource, extra: J) -> Result { + let mut data = extra + .as_object() + .cloned() + .ok_or(Error("invalid_handler_output"))?; + data.insert("generation".into(), json!(resource.generation)); + output(J::Object(data), vec![reference(resource)]) +} +pub(crate) fn assert_generation(ctx: &Context<'_>, resource: &Resource) -> Result<()> { + assert_expected( + ctx.request.field("expected_generations")?, + &resource.id, + resource.generation, + ) +} +fn assert_expected(values: &Json, id: &str, generation: i64) -> Result<()> { + for value in values.as_array()? { + let pair = value.as_array()?; + require(pair.len() == 2, "invalid_expected_generation")?; + if pair[0].as_str()? == id { + return require( + pair[1].as_integer()? == generation.to_string(), + "generation_conflict", + ); + } + } + Err(Error("expected_generation_required")) +} +fn valid_id(id: &str, code: &'static str) -> Result<()> { + require( + !id.is_empty() + && id.len() <= 128 + && id + .bytes() + .all(|b| b.is_ascii_alphanumeric() || b"_-".contains(&b)), + code, + ) +} +pub(crate) fn validate_name(name: &str) -> Result<()> { + require( + (1..=120).contains(&name.chars().count()) + && !matches!( + name, + "." | ".." | "json" | "meta" | "raw" | "history" | "revisions" + ) + && name + .chars() + .all(|c| c >= ' ' && c != '\u{7f}' && c != '/' && c != '\\'), + "invalid_name", + )?; + require(!name.starts_with(['@', '&', '!', '~']), "reserved_name") +} +fn normalize_tags(value: &Json) -> Result> { + let values = value.as_array().map_err(|_| Error("invalid_tags"))?; + require(values.len() <= 16, "invalid_tags")?; + let mut result = BTreeSet::new(); + for value in values { + let normalized: String = value + .as_str() + .map_err(|_| Error("invalid_tag"))? + .nfkc() + .collect(); + let tag: String = crate::unicode::casefold(normalized.trim_matches(py_space)) + .nfc() + .collect(); + require( + (1..=48).contains(&tag.chars().count()) + && tag.len() <= 96 + && tag.chars().next().is_some_and(char::is_alphanumeric) + && tag + .chars() + .all(|c| c.is_alphanumeric() || c == '-' || c == '_'), + "invalid_tag", + )?; + result.insert(tag); + } + Ok(result.into_iter().collect()) +} +fn summary(value: Option) -> Result> { + require( + value.as_ref().is_none_or(|s| s.chars().count() <= 280), + "invalid_summary", + )?; + Ok(value.filter(|s| !s.is_empty())) +} +fn setting(ctx: &Context<'_>, name: &str) -> Result> { + ctx.tx + .setting(name)? + .map(Json::into_object) + .transpose() + .map(|v| v.unwrap_or_default()) +} +fn policy(ctx: &Context<'_>, resource: &Resource) -> Result> { + let id = if resource.resource_type == "topic" { + &resource.id + } else { + resource.parent.as_ref().ok_or(Error("not_a_topic"))? + }; + setting(ctx, &format!("policy:{id}")) +} +fn editable(ctx: &Context<'_>, resource: &Resource) -> Result<()> { + require( + policy(ctx, resource)? + .get("editable") + .map(truthy) + .transpose()? + .unwrap_or(true), + "content_frozen", + ) +} +fn in_wiki(ctx: &Context<'_>, resource: &Resource) -> Result { + Ok(resource.id == "t_wiki" + || ctx + .tx + .ancestors(&resource.id)? + .iter() + .any(|r| r.id == "t_wiki")) +} +fn is_admin(ctx: &Context<'_>, topic: &str) -> Result { + if ctx.principal.subject() == Some(ROOT) { + return Ok(true); + } + let Some(subject) = ctx.principal.subject() else { + return Ok(false); + }; + let row = ctx.tx.one( + "SELECT role,status FROM topic_memberships WHERE topic=$1 AND subject=$2", + &[topic.into(), subject.into()], + )?; + Ok(row + .as_ref() + .is_some_and(|row| row.text(0) == Ok("admin") && row.text(1) == Ok("active"))) +} +fn can_present(ctx: &Context<'_>, topic: &Resource) -> Result { + Ok(ctx.principal.subject() == Some(topic.owner.as_str()) || is_admin(ctx, &topic.id)?) +} +fn presentation(ctx: &Context<'_>, resource: &Resource) -> Result { + if resource.resource_type != "file" + || !matches!(resource.name.as_str(), "ABOUT.md" | "HEADER.svg") + { + return Ok(false); + } + let Some(parent) = resource.parent.as_ref() else { + return Ok(false); + }; + let topic = ctx.tx.resource_record(parent)?; + if topic.resource_type != "topic" { + return Ok(false); + } + require(can_present(ctx, &topic)?, "topic_admin_required")?; + Ok(true) +} +fn protect_namespace(ctx: &mut Context<'_>, parent: &Resource, name: &str) -> Result<()> { + if parent.resource_type == "topic" && matches!(name, "ABOUT.md" | "HEADER.svg") { + require(can_present(ctx, parent)?, "topic_admin_required")?; + } + if name.starts_with('_') { + let operation = ctx.operation()?; + require( + ctx.authorizer.has( + ctx.principal, + "system.namespace", + &operation, + &parent.id, + ctx.tx, + ctx.now, + )?, + "protected_namespace", + )?; + } + Ok(()) +} +pub(crate) fn unmanaged(ctx: &Context<'_>, resource: &Resource) -> Result<()> { + require( + !matches!( + resource.resource_type.as_str(), + "order" | "order_collection" + ), + "order_controlled_resource", + )?; + let ancestors = ctx.tx.ancestors(&resource.id)?; + let mut parent_user = false; + for id in ancestors + .iter() + .map(|r| r.id.as_str()) + .chain(std::iter::once(resource.id.as_str())) + { + let child = ctx.tx.resource_record(id)?; + require( + !(parent_user + && matches!( + child.name.as_str(), + "SOUL.md" | "AGENTS.md" | "notes" | "todos" + )), + "personal_managed_resource", + )?; + parent_user = child.resource_type == "user"; + } + Ok(()) +} +fn removable(ctx: &mut Context<'_>, resource: &Resource) -> Result { + unmanaged(ctx, resource)?; + let ancestors = ctx.tx.ancestors(&resource.id)?; + require( + resource.id != "t_last_will" && !ancestors.iter().any(|r| r.id == "t_last_will"), + "legacy_directive_only", + )?; + require( + !matches!(resource.id.as_str(), "r_agents" | "r_rules") + && !ancestors.iter().any(|r| r.id == "r_rules"), + "system_managed_resource", + )?; + require(resource.id != ROOT, "protected_resource")?; + let parent = ctx.tx.resource_record( + resource + .parent + .as_deref() + .ok_or(Error("protected_resource"))?, + )?; + ctx.check(&parent.id, Check::Remove)?; + require( + parent.mode & STICKY == 0 + || ctx.principal.subject() == Some(resource.owner.as_str()) + || ctx.principal.subject() == Some(parent.owner.as_str()), + "sticky_denied", + )?; + Ok(parent) +} +fn enforce_rules(ctx: &Context<'_>, parent: &str) -> Result<()> { + let policy = setting(ctx, &format!("policy:{parent}"))?; + require( + policy + .get("editable") + .map(truthy) + .transpose()? + .unwrap_or(true), + "content_frozen", + )?; + let posting = policy + .get("posting_policy") + .map(Json::as_str) + .transpose()? + .unwrap_or("open"); + if posting == "open" { + return Ok(()); + } + let row = ctx + .tx + .one( + "SELECT role,status FROM topic_memberships WHERE topic=$1 AND subject=$2", + &[parent.into(), ctx.principal.subject().into()], + )? + .ok_or(Error("board_members_only"))?; + require(row.text(1)? == "active", "board_members_only")?; + require( + posting != "admins" || row.text(0)? == "admin", + "board_admins_only", + ) +} +fn direct_message(ctx: &Context<'_>, id: &str) -> Result { + let ids: Vec = ctx + .tx + .ancestors(id)? + .into_iter() + .map(|r| r.id) + .chain(std::iter::once(id.to_owned())) + .collect(); + Ok(ctx + .tx + .one( + "SELECT resource_id FROM dm_conversations WHERE resource_id=ANY($1) LIMIT 1", + &[Value::TextArray(ids)], + )? + .is_some()) +} + +enum Body { + Bytes(Vec, String), + Existing(BlobRef), +} +#[derive(Default)] +struct RevisionOptions { + relations: Vec, + author: Option, + signature: Option, + id: Option, + summary: Option, +} +fn revision_options(ctx: &Context<'_>) -> Result { + Ok(RevisionOptions { + signature: ctx + .args()? + .get("content_signature") + .map(decode) + .transpose()?, + id: argument(ctx, "revision_id")?, + summary: summary(argument(ctx, "summary")?)?, + ..RevisionOptions::default() + }) +} +struct Create { + parent: String, + kind: &'static str, + name: Option, + id: Option, + body: Option, + mode: Option, + revision: RevisionOptions, +} +fn create(ctx: &mut Context<'_>, input: Create) -> Result { + let parent = ctx.tx.resource_record(&input.parent)?; + require( + !matches!(input.kind, "order" | "order_collection") + && parent.resource_type != "order_collection", + "order_controlled_resource", + )?; + require(parent.state == ResourceState::Active, "ancestor_inactive")?; + require( + ctx.catalog + .resource_type(&parent.resource_type, parent.type_version as u32)? + .container, + "not_a_container", + )?; + let container = ctx.catalog.resource_type(input.kind, 1)?.container; + let presentation = parent.resource_type == "topic" + && input.kind == "file" + && matches!(input.name.as_deref(), Some("ABOUT.md" | "HEADER.svg")); + require( + parent.id != "t_store" || presentation, + "store_controlled_resource", + )?; + require( + parent.id != "t_last_will" || presentation, + "legacy_directive_only", + )?; + let mut name = input.name.unwrap_or(new_id("p")?); + if input.kind == "post" && !name.ends_with(".md") { + name.push_str(".md"); + } + validate_name(&name)?; + require( + !(parent.resource_type == "user" + && matches!(name.as_str(), "SOUL.md" | "AGENTS.md" | "notes" | "todos")), + "personal_managed_resource", + )?; + if matches!(parent.name.as_str(), "notes" | "todos") { + if let Some(id) = &parent.parent { + require( + ctx.tx.resource_record(id)?.resource_type != "user", + "personal_managed_resource", + )?; + } + } + protect_namespace(ctx, &parent, &name)?; + let subject = ctx.tx.subject(ctx.subject()?)?; + let policy = setting(ctx, &format!("policy:{}", parent.id))?; + let default = policy + .get(&format!("{}_mode", input.kind)) + .or_else(|| policy.get(if container { "topic_mode" } else { "post_mode" })) + .map(Json::as_str) + .transpose()? + .unwrap_or(if container { "1777" } else { "0644" }); + let mut mode = input + .mode + .unwrap_or(u16::from_str_radix(default, 8).map_err(|_| Error("invalid_mode"))?); + if container && parent.mode & SETGID != 0 { + mode |= SETGID; + } + if in_wiki(ctx, &parent)? { + require( + matches!(input.kind, "post" | "topic" | "file" | "attachment"), + "wiki_content_only", + )?; + mode = if container { 0o1777 } else { 0o666 }; + } + let id = input.id.unwrap_or(new_id("r")?); + valid_id(&id, "invalid_resource_id")?; + let actor = ctx + .principal + .actor() + .ok_or(Error("authentication_required"))? + .to_owned(); + let resource = Resource { + id, + resource_type: input.kind.into(), + type_version: 1, + name, + parent: Some(parent.id), + owner: subject.resource_id, + group: if parent.mode & SETGID != 0 { + parent.group + } else { + subject.primary_group + }, + mode, + generation: 0, + revision: None, + state: ResourceState::Active, + created_at: ctx.now, + created_by: actor.clone(), + modified_at: ctx.now, + modified_by: actor, + tags: vec![], + }; + ctx.tx.insert_resource(&resource)?; + match input.body { + Some(body) => revise(ctx, resource, body, input.revision), + None => Ok(resource), + } +} +fn manifest(revision: &Revision) -> Result { + let mut value = encode(revision)?.into_object()?; + value.remove("manifest_digest"); + value.remove("signature"); + Ok(Json::from_object(value)) +} +fn revise( + ctx: &mut Context<'_>, + resource: Resource, + body: Body, + options: RevisionOptions, +) -> Result { + let presented = presentation(ctx, &resource)?; + require( + !matches!( + resource.resource_type.as_str(), + "order" | "order_collection" + ), + "order_controlled_resource", + )?; + let timestamp = argument(ctx, "content_created_at")?; + require( + options.signature.is_some() || (options.id.is_none() && timestamp.is_none()), + "content_signature_required", + )?; + require( + options.signature.is_none() || options.id.is_some(), + "revision_id_required", + )?; + let blob = match body { + Body::Existing(blob) => { + ctx.contents.read_to(&blob, None, &mut io::sink())?; + blob + } + Body::Bytes(bytes, media) => { + require( + bytes.len() <= ctx.settings.max_request_bytes, + "use_transfer", + )?; + ctx.put_bytes(&bytes, &media, None)? + } + }; + if presented { + if resource.name == "HEADER.svg" { + require( + blob.media_type == "image/svg+xml" && blob.size <= 98_304, + "invalid_board_svg", + )?; + safe_svg(&ctx.contents.read_bytes(&blob, 98_304)?)?; + } else { + require( + matches!(blob.media_type.as_str(), "text/plain" | "text/markdown") + && blob.size <= 4096, + "invalid_board_description", + )?; + } + } + for relation in &options.relations { + require( + ctx.catalog + .resource_type(&resource.resource_type, resource.type_version as u32)? + .relations + .contains(&relation.relation_type), + "invalid_relation_type", + )?; + ctx.check(&relation.target.id, Check::Read)?; + if relation.target.revision.is_some() { + ctx.tx.revision(&relation.target)?; + } + } + let id = options.id.unwrap_or(new_id("v")?); + valid_id(&id, "invalid_revision_id")?; + let created_at = if options.signature.is_some() { + let time = Timestamp::parse( + timestamp + .as_deref() + .ok_or(Error("content_timestamp_required"))?, + )?; + require( + (time.0 - ctx.now.0).num_milliseconds().abs() <= 300_000, + "content_timestamp_out_of_range", + )?; + time + } else { + ctx.now + }; + let subject = ctx.subject()?.to_owned(); + let custodial = ctx.principal.method() == "token" + && ctx.tx.subject(&subject)?.kind == SubjectKind::Custodial; + require( + !custodial || options.signature.is_none(), + "custodial_content_signature_forbidden", + )?; + let mut revision = Revision { + format_version: 1, + id, + resource_id: resource.id.clone(), + parents: resource.revision.iter().cloned().collect(), + content: blob, + relations: options.relations, + actor: ctx + .principal + .actor() + .ok_or(Error("authentication_required"))? + .into(), + subject: subject.clone(), + author: options.author.unwrap_or(subject.clone()), + created_at, + manifest_digest: String::new(), + signature: None, + summary: summary(options.summary)?, + change_note: None, + source_kind: custodial.then_some(SourceKind::Operation), + source_version: if custodial { + Some( + ctx.request + .field("contract_version")? + .as_integer()? + .parse() + .map_err(|_| Error("invalid_version"))?, + ) + } else { + None + }, + source_digest: if custodial { + Some(ctx.request.payload_digest()?) + } else { + None + }, + signature_source: custodial.then_some(SignatureSource::Custodial), + }; + let signed = manifest(&revision)?.canonical()?; + revision.manifest_digest = digest(signed.as_bytes()); + if let Some(signature) = options.signature { + let credential = ctx.tx.credential( + ctx.principal + .credential_id() + .ok_or(Error("content_signer_mismatch"))?, + )?; + require(signature.key_id == credential.id, "content_signer_mismatch")?; + verify( + &credential.verifier.0, + signed.as_bytes(), + &signature, + "revision", + )?; + revision.signature = Some(signature); + } else if custodial { + let signer = msg_identity::vault::open_signer( + ctx.tx, + &ctx.settings.token_secret, + &subject, + ctx.now, + )?; + revision.signature = Some(signer.sign(signed.as_bytes(), "revision")?); + } + ctx.pin(&revision.content, &revision.id)?; + let ancestors = ctx.tx.ancestors(&resource.id)?; + let topic = ancestors + .iter() + .rev() + .find(|r| r.kind == "topic") + .map(|r| r.id.as_str()) + .unwrap_or(ROOT_SPACE); + ctx.commit_revision(topic, &revision)?; + ctx.tx.append_revision(&revision)?; + let mut updated = resource.clone(); + updated.generation = resource + .generation + .checked_add(1) + .ok_or(Error("generation_exhausted"))?; + updated.revision = Some(revision.id); + updated.modified_at = ctx.now; + updated.modified_by = revision.actor; + ctx.tx.replace_resource(&updated, resource.generation)?; + if revision.content.media_type.starts_with("text/") && revision.content.size <= 1_048_576 { + let bytes = ctx.contents.read_bytes(&revision.content, 1_048_576)?; + let text = String::from_utf8_lossy(&bytes).into_owned(); + ctx.tx.execute("INSERT INTO projections(resource_id,text) VALUES($1,$2) ON CONFLICT(resource_id) DO UPDATE SET text=excluded.text", &[resource.id.into(), text.into()])?; + } else { + ctx.tx.execute( + "DELETE FROM projections WHERE resource_id=$1", + &[resource.id.into()], + )?; + } + Ok(updated) +} + +// The snapshot reader verifies digest/length before this bounded incremental +// UTF-8 validator sees any bytes. Source imports never rewrite signed content. +#[derive(Default)] +struct Utf8 { + tail: Vec, + invalid: bool, +} +impl Write for Utf8 { + fn write(&mut self, bytes: &[u8]) -> io::Result { + let mut data = std::mem::take(&mut self.tail); + data.extend_from_slice(bytes); + match std::str::from_utf8(&data) { + Ok(_) => (), + Err(e) if e.error_len().is_none() => { + self.tail.extend_from_slice(&data[e.valid_up_to()..]) + } + Err(_) => { + self.invalid = true; + return Err(io::Error::new(io::ErrorKind::InvalidData, "invalid_utf8")); + } + } + Ok(bytes.len()) + } + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} +fn source_content(ctx: &mut Context<'_>, value: &Json) -> Result { + let reference: ResourceRef = decode(value)?; + require(reference.revision.is_some(), "source_revision_required")?; + ctx.check(&reference.id, Check::Read)?; + require(!direct_message(ctx, &reference.id)?, "dm_reference_private")?; + let revision = ctx.tx.revision(&reference)?; + require( + matches!( + revision.content.media_type.as_str(), + "text/plain" | "text/markdown" + ), + "text_source_required", + )?; + let mut validator = Utf8::default(); + let result = ctx + .contents + .read_to(&revision.content, None, &mut validator); + require( + !validator.invalid && validator.tail.is_empty(), + "invalid_utf8", + )?; + result?; + Ok(Body::Existing(revision.content)) +} + +fn topic_create(ctx: &mut Context<'_>) -> Result { + let input = Create { + parent: ctx.resolve(ctx.text("parent")?, false)?, + kind: "topic", + name: Some(ctx.text("name")?.into()), + id: None, + body: None, + mode: None, + revision: RevisionOptions::default(), + }; + let resource = create(ctx, input)?; + ctx.tx.execute( + "INSERT INTO topic_settings(topic,membership_policy) VALUES($1,'open')", + &[resource.id.as_str().into()], + )?; + let role = if in_wiki(ctx, &resource)? { + "member" + } else { + "admin" + }; + ctx.tx.execute("INSERT INTO topic_memberships(topic,subject,role,status,joined_at,invited_by) VALUES($1,$2,$3,'active',$4,NULL)", &[resource.id.as_str().into(), ctx.subject()?.into(), role.into(), encode(&ctx.now)?.as_str()?.into()])?; + governance_event(ctx, &resource.id, "create", ctx.principal.subject(), None)?; + result(&resource, json!({})) +} +fn post_body( + ctx: &mut Context<'_>, + parent: &str, + old: Option<&Revision>, + options: &mut RevisionOptions, +) -> Result<(Body, J)> { + let args = ctx.args()?.clone(); + if let Some(template) = args.get("template").filter(|v| !v.is_null()) { + require( + !args.contains_key("body") && !args.contains_key("source"), + "ambiguous_content", + )?; + let (body, metadata, relation) = + template_content(ctx, parent, template, args.get("values"))?; + options.relations.push(relation); + Ok((body, metadata)) + } else if let Some(source) = args.get("source").filter(|v| !v.is_null()) { + require(!args.contains_key("body"), "ambiguous_content")?; + Ok((source_content(ctx, source)?, json!({}))) + } else if args.contains_key("summary") && !args.contains_key("body") && old.is_some() { + let old = old.ok_or(Error("body_required"))?; + options.relations = old.relations.clone(); + Ok((Body::Existing(old.content.clone()), json!({}))) + } else { + let body = args.get("body").ok_or(Error("body_required"))?.as_str()?; + Ok(( + Body::Bytes(body.as_bytes().to_vec(), "text/markdown".into()), + json!({}), + )) + } +} +fn post_create(ctx: &mut Context<'_>) -> Result { + let parent = ctx.resolve(ctx.text("parent")?, false)?; + enforce_rules(ctx, &parent)?; + let mut revision = revision_options(ctx)?; + let (body, metadata) = post_body(ctx, &parent, None, &mut revision)?; + let input = Create { + parent, + kind: "post", + name: argument(ctx, "name")?, + id: argument(ctx, "resource_id")?, + body: Some(body), + mode: None, + revision, + }; + let resource = create(ctx, input)?; + result(&resource, metadata) +} +fn post_edit(ctx: &mut Context<'_>) -> Result { + let resource = ctx + .tx + .resource_record(&ctx.resolve(ctx.text("id")?, false)?)?; + require( + resource.resource_type == "post" && resource.state == ResourceState::Active, + "not_editable", + )?; + assert_generation(ctx, &resource)?; + require( + resource.revision.as_deref() == Some(ctx.text("expected_revision")?), + "revision_conflict", + )?; + editable(ctx, &resource)?; + let old = ctx.tx.revision(&reference(&resource))?; + let mut options = revision_options(ctx)?; + if !ctx.args()?.contains_key("summary") { + options.summary = old.summary.clone(); + } + options.author = Some(old.author.clone()); + options.relations = old + .relations + .iter() + .filter(|r| r.relation_type != "template") + .cloned() + .collect(); + let (body, _) = post_body( + ctx, + resource.parent.as_deref().ok_or(Error("not_a_topic"))?, + Some(&old), + &mut options, + )?; + let updated = revise(ctx, resource, body, options)?; + result(&updated, json!({})) +} +fn change(ctx: &Context<'_>, mut resource: Resource) -> Result { + let generation = resource.generation; + resource.generation = generation + .checked_add(1) + .ok_or(Error("generation_exhausted"))?; + resource.modified_at = ctx.now; + resource.modified_by = ctx + .principal + .actor() + .ok_or(Error("authentication_required"))? + .into(); + ctx.tx.replace_resource(&resource, generation)?; + Ok(resource) +} +fn tags_set(ctx: &mut Context<'_>) -> Result { + let mut resource = ctx + .tx + .resource_record(&ctx.resolve(ctx.text("id")?, false)?)?; + unmanaged(ctx, &resource)?; + ctx.check( + &resource.id, + if matches!(resource.resource_type.as_str(), "topic" | "repo") { + Check::Manage + } else { + Check::Write + }, + )?; + require( + ctx.catalog + .resource_type(&resource.resource_type, resource.type_version as u32)? + .taggable, + "resource_not_taggable", + )?; + require(resource.state == ResourceState::Active, "resource_inactive")?; + assert_generation(ctx, &resource)?; + let tags = normalize_tags(ctx.arg("tags")?)?; + if resource.tags != tags { + resource.tags = tags; + resource = change(ctx, resource)?; + } + result(&resource, json!({"tags":resource.tags})) +} +fn post_metadata(ctx: &mut Context<'_>) -> Result { + let mut resource = ctx + .tx + .resource_record(&ctx.resolve(ctx.text("id")?, false)?)?; + require( + ctx.args()?.contains_key("name") || ctx.args()?.contains_key("tags"), + "metadata_change_required", + )?; + require( + resource.resource_type == "post" && resource.state == ResourceState::Active, + "not_editable", + )?; + unmanaged(ctx, &resource)?; + assert_generation(ctx, &resource)?; + editable(ctx, &resource)?; + let mut name = resource.name.clone(); + if let Some(supplied) = argument(ctx, "name")? { + let parent = removable(ctx, &resource)?; + name = supplied; + if !name.ends_with(".md") { + name.push_str(".md"); + } + validate_name(&name)?; + protect_namespace(ctx, &parent, &name)?; + } + let tags = ctx + .args()? + .get("tags") + .map(normalize_tags) + .transpose()? + .unwrap_or(resource.tags.clone()); + if name != resource.name || tags != resource.tags { + resource.name = name; + resource.tags = tags; + resource = change(ctx, resource)?; + } + result( + &resource, + json!({"name":resource.name,"tags":resource.tags}), + ) +} +fn post_rollback(ctx: &mut Context<'_>) -> Result { + let resource = ctx + .tx + .resource_record(&ctx.resolve(ctx.text("id")?, false)?)?; + require( + resource.resource_type == "post" && resource.state == ResourceState::Active, + "not_editable", + )?; + unmanaged(ctx, &resource)?; + assert_generation(ctx, &resource)?; + editable(ctx, &resource)?; + require( + resource.revision.as_deref() == Some(ctx.text("base_revision")?), + "revision_conflict", + )?; + require( + resource.revision.as_deref() != Some(ctx.text("target_revision")?), + "rollback_target_current", + )?; + let current = ctx.tx.revision(&reference(&resource))?; + let old = ctx.tx.revision(&ResourceRef { + id: resource.id.clone(), + revision: Some(ctx.text("target_revision")?.into()), + })?; + require(old.author == current.author, "rollback_author_mismatch")?; + let signed = manifest(&old)?.canonical()?; + require( + digest(signed.as_bytes()) == old.manifest_digest, + "revision_manifest_mismatch", + )?; + if let Some(signature) = &old.signature { + let credential = ctx.tx.credential(&signature.key_id)?; + require( + credential.subject_id == old.subject && credential.kind == CredentialKind::SigningKey, + "revision_signer_mismatch", + )?; + verify( + &credential.verifier.0, + signed.as_bytes(), + signature, + "revision", + )?; + } + let mut options = revision_options(ctx)?; + options.author = Some(current.author); + options.summary = old.summary; + options.relations = old.relations; + let updated = revise(ctx, resource, Body::Existing(old.content), options)?; + result(&updated, json!({"rolled_back_from":old.id})) +} +fn file_put(ctx: &mut Context<'_>) -> Result { + let args = ctx.args()?.clone(); + require( + args.contains_key("data") != args.contains_key("source"), + "one_content_source_required", + )?; + let media = argument(ctx, "media_type")?.unwrap_or("application/octet-stream".into()); + require(!media.contains(['\r', '\n']), "invalid_media_type")?; + let body = if let Some(source) = args.get("source") { + let reference: ResourceRef = decode(source)?; + ctx.check(&reference.id, Check::Read)?; + Body::Existing(ctx.tx.revision(&reference)?.content) + } else { + Body::Bytes( + unb64(ctx.text("data")?, ctx.settings.max_request_bytes)?, + media, + ) + }; + let input = Create { + parent: ctx.resolve(ctx.text("parent")?, false)?, + kind: "file", + name: Some(ctx.text("name")?.into()), + id: None, + body: Some(body), + mode: None, + revision: RevisionOptions::default(), + }; + let resource = create(ctx, input)?; + result(&resource, json!({})) +} +fn attach(ctx: &mut Context<'_>) -> Result { + let post = ctx + .tx + .resource_record(&ctx.resolve(ctx.text("post")?, false)?)?; + require( + post.resource_type == "post" && post.state == ResourceState::Active, + "not_a_post", + )?; + assert_generation(ctx, &post)?; + ctx.check(&post.id, Check::Write)?; + let source: ResourceRef = decode(ctx.arg("source")?)?; + ctx.check(&source.id, Check::Read)?; + let revision = ctx.tx.revision(&source)?; + let parent = post.parent.clone().ok_or(Error("not_a_topic"))?; + ctx.check(&parent, Check::Create)?; + let input = Create { + parent, + kind: "attachment", + name: Some(argument(ctx, "name")?.unwrap_or(new_id("attachment")?)), + id: None, + body: Some(Body::Existing(revision.content)), + mode: Some(post.mode & 0o777), + revision: RevisionOptions::default(), + }; + let attached = create(ctx, input)?; + ctx.tx.set_setting( + &format!("publication:{}", attached.id), + &encode(&json!({"source":source,"post":post.id,"publisher":ctx.subject()?}))?, + )?; + let mut old = ctx.tx.revision(&reference(&post))?; + old.relations.push(Relation { + relation_type: "attachment".into(), + target: reference(&attached), + excerpt: None, + }); + let updated = revise( + ctx, + post, + Body::Existing(old.content), + RevisionOptions { + relations: old.relations, + author: Some(old.author), + summary: old.summary, + ..RevisionOptions::default() + }, + )?; + result(&updated, json!({"attachment":reference(&attached)})) +} +fn topic_configure(ctx: &mut Context<'_>) -> Result { + let resource = ctx + .tx + .resource_record(&ctx.resolve(ctx.text("id")?, false)?)?; + unmanaged(ctx, &resource)?; + require(resource.resource_type == "topic", "not_a_topic")?; + assert_generation(ctx, &resource)?; + ctx.tx + .set_setting(&format!("policy:{}", resource.id), ctx.arg("policy")?)?; + let updated = change(ctx, resource)?; + governance_event(ctx, &updated.id, "configure", ctx.principal.subject(), None)?; + result(&updated, json!({})) +} +fn governance_event( + ctx: &Context<'_>, + topic: &str, + action: &str, + target: Option<&str>, + reason: Option<&str>, +) -> Result<()> { + let mut data = json!({"topic_id":topic,"action":action,"target_subject":target}); + if let Some(reason) = reason.filter(|r| !r.is_empty()) { + data["reason"] = json!(reason); + } + let event = Event { + id: new_id("te")?, + event_type: format!("topic.{action}"), + time: ctx.now, + request_id: ctx.request.field("request_id")?.as_str()?.into(), + actor: ctx.principal.actor().map(str::to_owned), + subject: ctx.principal.subject().map(str::to_owned), + resources: vec![ResourceRef { + id: topic.into(), + revision: None, + }], + data: encode(&data)?.into_object()?, + }; + ctx.tx.append_event(&event)?; + ctx.tx.execute( + "INSERT INTO topic_event_projection(seq,topic) SELECT seq,$1 FROM events WHERE id=$2", + &[topic.into(), event.id.as_str().into()], + )?; + if let Some(target) = target { + let id = new_id("message")?; + let notice = encode( + &json!({"id":id,"sender":ctx.principal.subject(),"actor":ctx.principal.actor(),"recipient":target,"resource":{"id":target,"revision":null},"time":ctx.now,"state":"delivered","source":"topic_governance","topic_id":topic,"action":action}), + )?; + ctx.tx.execute("INSERT INTO messages(id,sender,recipient,resource,event_id,body) VALUES($1,$2,$3,$3,$4,$5)", &[id.into(),ctx.principal.subject().into(),target.into(),event.id.into(),notice.canonical()?.into()])?; + } + Ok(()) +} + +fn safe_svg(bytes: &[u8]) -> Result<()> { + const NS: &str = "http://www.w3.org/2000/svg"; + let error = Error("invalid_board_svg"); + require(!bytes.is_empty() && bytes.len() <= 98_304, error.0)?; + let text = std::str::from_utf8(bytes).map_err(|_| error)?; + let lower = crate::unicode::casefold(&text.replace(NS, "")); + require( + ![ + ", + default: Option, +} +struct Template { + name: String, + version: i64, + fields: Vec, +} +fn py_space(c: char) -> bool { + c.is_whitespace() || ('\u{1c}'..='\u{1f}').contains(&c) +} +fn template_name(name: &str) -> bool { + name.chars().next().is_some_and(|c| c.is_ascii_alphabetic()) + && name + .chars() + .all(|c| c.is_alphanumeric() || matches!(c, '_' | '.' | '-')) +} +fn check_field(field: &Field, value: &Json) -> Result<()> { + if value.is_null() && !field.required { + return Ok(()); + } + match field.kind.as_str() { + "str" | "text" | "enum" => { + value.as_str().map_err(|_| Error("template_type_error"))?; + } + "int" => { + value + .as_integer() + .map_err(|_| Error("template_type_error"))?; + } + "bool" => { + value.as_bool().map_err(|_| Error("template_type_error"))?; + } + "ref" | "file" => { + if let Ok(id) = value.as_str() { + require( + !id.is_empty() + && id.len() <= 128 + && id + .bytes() + .all(|b| b.is_ascii_alphanumeric() || b"_.:-".contains(&b)), + "template_reference_error", + )?; + } else { + let _: ResourceRef = decode(value)?; + } + } + _ => return Err(Error("unknown_field_type")), + } + if field.kind == "enum" { + require( + field + .choices + .iter() + .any(|c| value.as_str() == Ok(c.as_str())), + "template_enum_error", + )?; + } + Ok(()) +} +fn parse_template(source: &str) -> Result