cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

farm.rs (67001B)


      1 use std::sync::atomic::{AtomicU64, Ordering};
      2 use std::time::{SystemTime, UNIX_EPOCH};
      3 
      4 use radroots_authority::RadrootsActorContext;
      5 use radroots_events::contract::RadrootsActorRole;
      6 use radroots_events::farm::{RadrootsFarm, RadrootsFarmLocation};
      7 use radroots_events::kinds::{KIND_FARM, KIND_PROFILE};
      8 use radroots_events::listing::RadrootsListingLocation;
      9 use radroots_events::profile::{RadrootsProfile, RadrootsProfileType};
     10 use radroots_events_codec::d_tag::is_d_tag_base64url;
     11 use radroots_events_codec::profile::encode::to_wire_parts_with_profile_type;
     12 use radroots_sdk::{
     13     FarmEnqueuePublishRequest, FarmEnqueueReceipt, FarmPreparePublishRequest, FarmPublishPlan,
     14     PushOutboxEventReceipt, PushOutboxEventState, PushOutboxReceipt, PushOutboxRelayOutcomeKind,
     15     PushOutboxRequest, SdkMutationState,
     16 };
     17 use serde_json::json;
     18 
     19 use crate::cli::global::{
     20     FarmCreateArgs, FarmFieldArg, FarmPublishArgs, FarmRebindArgs, FarmScopeArg, FarmScopedArgs,
     21     FarmUpdateArgs,
     22 };
     23 use crate::runtime::RuntimeError;
     24 use crate::runtime::account::{self, AccountRecordView};
     25 use crate::runtime::config::{PublishTransport, RuntimeConfig, SignerBackend};
     26 use crate::runtime::farm_config::{
     27     self, FarmConfigDocument, FarmConfigScope, FarmConfigSelection, FarmListingDefaults,
     28     FarmMissingField, FarmPublicationStatus, ResolvedFarmConfig, SUPPORTED_FARM_CONFIG_VERSION,
     29 };
     30 use crate::runtime::local_events::append_local_work;
     31 use crate::runtime::sdk::{
     32     CliSdkAdapterError, CliSdkSession, sdk_relay_target_policy, sdk_relay_url_policy,
     33     validate_configured_signer_for_actor,
     34 };
     35 use crate::runtime::signer::ActorWriteBindingError;
     36 use crate::view::runtime::{
     37     FarmConfigDocumentView, FarmConfigSummaryView, FarmGetView, FarmListingDefaultsView,
     38     FarmPublicationView, FarmPublishComponentView, FarmPublishEventView, FarmPublishView,
     39     FarmRebindView, FarmSelectionView, FarmSetView, FarmSetupView, FarmStatusView,
     40     RelayFailureView,
     41 };
     42 
     43 const FARM_CONFIG_SOURCE: &str = "farm config · local first";
     44 const FARM_SELLER_ACTOR_SOURCE: &str = "farm_config";
     45 const SDK_FARM_WRITE_SOURCE: &str = "SDK farm publish · configured signer";
     46 const SDK_PROFILE_NOT_SUBMITTED_METHOD: &str = "sdk.farm.profile.not_submitted";
     47 const SDK_FARM_PUBLISH_METHOD: &str = "sdk.farm.publish.v1";
     48 const SDK_PROFILE_NOT_SUBMITTED_REASON: &str =
     49     "profile publish is not part of SDK farm.publish.v1; profile draft was not submitted";
     50 
     51 static D_TAG_COUNTER: AtomicU64 = AtomicU64::new(0);
     52 
     53 pub fn init(config: &RuntimeConfig, args: &FarmCreateArgs) -> Result<FarmSetupView, RuntimeError> {
     54     let scope = scope_from_arg(args.scope);
     55     let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
     56     let Some(selected_account) = selected_account_for_draft(config)? else {
     57         return Ok(missing_selected_account_setup_view());
     58     };
     59     let existing = farm_config::load(config, Some(resolved_scope))?;
     60     let document = init_document(resolved_scope, &selected_account, existing.as_ref(), args)?;
     61     save_draft_view(
     62         "saved",
     63         resolved_scope,
     64         &selected_account,
     65         &document,
     66         Some("The farm draft is local until you publish it.".to_owned()),
     67         farm_setup_actions(config, &document, Some(&selected_account)),
     68         config,
     69     )
     70 }
     71 
     72 pub fn init_preflight(
     73     config: &RuntimeConfig,
     74     args: &FarmCreateArgs,
     75 ) -> Result<FarmSetupView, RuntimeError> {
     76     let scope = scope_from_arg(args.scope);
     77     let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
     78     let Some(selected_account) = selected_account_for_draft(config)? else {
     79         return Ok(missing_selected_account_setup_view());
     80     };
     81     let existing = farm_config::load(config, Some(resolved_scope))?;
     82     let document = init_document(resolved_scope, &selected_account, existing.as_ref(), args)?;
     83     let path = farm_config::config_path(&config.paths, resolved_scope)?;
     84     Ok(FarmSetupView {
     85         state: "dry_run".to_owned(),
     86         source: FARM_CONFIG_SOURCE.to_owned(),
     87         config: Some(summary_view(
     88             resolved_scope,
     89             path.display().to_string(),
     90             &document,
     91             Some(
     92                 selected_account
     93                     .record
     94                     .public_identity
     95                     .public_key_hex
     96                     .as_str(),
     97             ),
     98         )),
     99         reason: Some("dry run requested; farm draft was not written".to_owned()),
    100         actions: farm_setup_actions(config, &document, Some(&selected_account)),
    101     })
    102 }
    103 
    104 pub fn rebind(
    105     config: &RuntimeConfig,
    106     args: &FarmRebindArgs,
    107 ) -> Result<FarmRebindView, RuntimeError> {
    108     rebind_inner(config, args, false)
    109 }
    110 
    111 pub fn rebind_preflight(
    112     config: &RuntimeConfig,
    113     args: &FarmRebindArgs,
    114 ) -> Result<FarmRebindView, RuntimeError> {
    115     rebind_inner(config, args, true)
    116 }
    117 
    118 fn rebind_inner(
    119     config: &RuntimeConfig,
    120     args: &FarmRebindArgs,
    121     dry_run: bool,
    122 ) -> Result<FarmRebindView, RuntimeError> {
    123     let scope = scope_from_arg(args.scope);
    124     let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
    125     let path = farm_config::config_path(&config.paths, resolved_scope)?;
    126     let Some(resolved) = farm_config::load(config, Some(resolved_scope))? else {
    127         return Ok(FarmRebindView {
    128             state: "unconfigured".to_owned(),
    129             source: FARM_CONFIG_SOURCE.to_owned(),
    130             scope: resolved_scope.as_str().to_owned(),
    131             path: path.display().to_string(),
    132             config_present: false,
    133             dry_run,
    134             seller_actor_source: FARM_SELLER_ACTOR_SOURCE.to_owned(),
    135             from_seller_account_id: None,
    136             from_seller_pubkey: None,
    137             to_seller_account_id: None,
    138             to_seller_pubkey: None,
    139             seller_pubkey_changed: None,
    140             publication_state_action: None,
    141             config: None,
    142             reason: Some(format!("no farm config found at {}", path.display())),
    143             actions: vec!["radroots farm create".to_owned()],
    144         });
    145     };
    146 
    147     let from_account = configured_account(config, &resolved.document.selection.account)?;
    148     let from_seller_pubkey = from_account
    149         .as_ref()
    150         .map(|account| account.record.public_identity.public_key_hex.clone());
    151     let target_account = account::resolve_account_selector(config, args.selector.as_str())
    152         .map_err(|error| farm_rebind_selector_error(args.selector.as_str(), error))?;
    153     let to_seller_pubkey = target_account.record.public_identity.public_key_hex.clone();
    154     let seller_pubkey_changed = from_seller_pubkey
    155         .as_deref()
    156         .is_none_or(|pubkey| !pubkey.eq_ignore_ascii_case(to_seller_pubkey.as_str()));
    157     let publication_state_action = if seller_pubkey_changed {
    158         "cleared"
    159     } else {
    160         "preserved"
    161     };
    162     let mut document = resolved.document.clone();
    163     document.selection.account = target_account.record.account_id.to_string();
    164     if seller_pubkey_changed {
    165         document.publication = FarmPublicationStatus::default();
    166     }
    167     let written_path = if dry_run {
    168         resolved.path.clone()
    169     } else {
    170         let written_path = farm_config::write(&config.paths, resolved.scope, &document)?;
    171         append_farm_local_work(
    172             config,
    173             resolved.scope,
    174             written_path.display().to_string(),
    175             &document,
    176             Some(to_seller_pubkey.as_str()),
    177         )?;
    178         written_path
    179     };
    180     let state = if dry_run { "dry_run" } else { "rebound" };
    181 
    182     Ok(FarmRebindView {
    183         state: state.to_owned(),
    184         source: FARM_CONFIG_SOURCE.to_owned(),
    185         scope: resolved.scope.as_str().to_owned(),
    186         path: written_path.display().to_string(),
    187         config_present: true,
    188         dry_run,
    189         seller_actor_source: FARM_SELLER_ACTOR_SOURCE.to_owned(),
    190         from_seller_account_id: Some(resolved.document.selection.account.clone()),
    191         from_seller_pubkey,
    192         to_seller_account_id: Some(target_account.record.account_id.to_string()),
    193         to_seller_pubkey: Some(to_seller_pubkey.clone()),
    194         seller_pubkey_changed: Some(seller_pubkey_changed),
    195         publication_state_action: Some(publication_state_action.to_owned()),
    196         config: Some(summary_view(
    197             resolved.scope,
    198             written_path.display().to_string(),
    199             &document,
    200             Some(to_seller_pubkey.as_str()),
    201         )),
    202         reason: Some(if dry_run {
    203             "dry run requested; farm seller binding was not written".to_owned()
    204         } else {
    205             "farm seller binding updated".to_owned()
    206         }),
    207         actions: if dry_run {
    208             vec![format!(
    209                 "radroots --approval-token approve farm rebind {}",
    210                 args.selector
    211             )]
    212         } else {
    213             vec!["radroots farm readiness check".to_owned()]
    214         },
    215     })
    216 }
    217 
    218 fn farm_rebind_selector_error(selector: &str, error: RuntimeError) -> RuntimeError {
    219     match error {
    220         RuntimeError::Account(account::AccountRuntimeFailure::Unresolved(issue)) => {
    221             account::AccountRuntimeFailure::unresolved_with_detail(
    222                 issue.message().to_owned(),
    223                 json!({
    224                     "seller_actor_source": FARM_SELLER_ACTOR_SOURCE,
    225                     "selector": selector,
    226                     "actions": account_recovery_actions(),
    227                 }),
    228             )
    229             .into()
    230         }
    231         other => other,
    232     }
    233 }
    234 
    235 pub fn set(config: &RuntimeConfig, args: &FarmUpdateArgs) -> Result<FarmSetView, RuntimeError> {
    236     let scope = scope_from_arg(args.scope);
    237     let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
    238     let path = farm_config::config_path(&config.paths, resolved_scope)?;
    239     let Some(mut resolved) = farm_config::load(config, Some(resolved_scope))? else {
    240         return Ok(FarmSetView {
    241             state: "unconfigured".to_owned(),
    242             source: FARM_CONFIG_SOURCE.to_owned(),
    243             field: human_field_name(args.field).to_owned(),
    244             value: human_field_value(args.field, args.value.join(" ").trim()).to_owned(),
    245             config: None,
    246             reason: Some(format!("no farm draft found at {}", path.display())),
    247             actions: vec!["radroots farm create".to_owned()],
    248         });
    249     };
    250 
    251     let raw_value = args.value.join(" ");
    252     let field_value = required_text(raw_value.as_str(), "farm set value")?;
    253     apply_field_update(&mut resolved.document, args.field, field_value.as_str())?;
    254     let written_path = farm_config::write(&config.paths, resolved.scope, &resolved.document)?;
    255     let configured_account = configured_account(config, &resolved.document.selection.account)?;
    256     let account_pubkey = configured_account
    257         .as_ref()
    258         .map(|account| account.record.public_identity.public_key_hex.as_str());
    259     append_farm_local_work(
    260         config,
    261         resolved.scope,
    262         written_path.display().to_string(),
    263         &resolved.document,
    264         account_pubkey,
    265     )?;
    266     let reason = if configured_account.is_none() {
    267         Some(missing_farm_bound_seller_reason(
    268             resolved.document.selection.account.as_str(),
    269         ))
    270     } else {
    271         None
    272     };
    273 
    274     Ok(FarmSetView {
    275         state: "updated".to_owned(),
    276         source: FARM_CONFIG_SOURCE.to_owned(),
    277         field: human_field_name(args.field).to_owned(),
    278         value: human_field_value(args.field, field_value.as_str()).to_owned(),
    279         config: Some(summary_view(
    280             resolved.scope,
    281             written_path.display().to_string(),
    282             &resolved.document,
    283             account_pubkey,
    284         )),
    285         reason,
    286         actions: farm_update_actions(config, &resolved.document, configured_account.as_ref()),
    287     })
    288 }
    289 
    290 pub fn set_preflight(
    291     config: &RuntimeConfig,
    292     args: &FarmUpdateArgs,
    293 ) -> Result<FarmSetView, RuntimeError> {
    294     let scope = scope_from_arg(args.scope);
    295     let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
    296     let path = farm_config::config_path(&config.paths, resolved_scope)?;
    297     let Some(mut resolved) = farm_config::load(config, Some(resolved_scope))? else {
    298         return Ok(FarmSetView {
    299             state: "unconfigured".to_owned(),
    300             source: FARM_CONFIG_SOURCE.to_owned(),
    301             field: human_field_name(args.field).to_owned(),
    302             value: human_field_value(args.field, args.value.join(" ").trim()).to_owned(),
    303             config: None,
    304             reason: Some(format!("no farm draft found at {}", path.display())),
    305             actions: vec!["radroots farm create".to_owned()],
    306         });
    307     };
    308 
    309     let raw_value = args.value.join(" ");
    310     let field_value = required_text(raw_value.as_str(), "farm set value")?;
    311     apply_field_update(&mut resolved.document, args.field, field_value.as_str())?;
    312     let configured_account = configured_account(config, &resolved.document.selection.account)?;
    313     let account_pubkey = configured_account
    314         .as_ref()
    315         .map(|account| account.record.public_identity.public_key_hex.as_str());
    316     let reason = if configured_account.is_none() {
    317         Some(format!(
    318             "dry run requested; farm draft was not written; {}",
    319             missing_farm_bound_seller_reason(resolved.document.selection.account.as_str())
    320         ))
    321     } else {
    322         Some("dry run requested; farm draft was not written".to_owned())
    323     };
    324 
    325     Ok(FarmSetView {
    326         state: "dry_run".to_owned(),
    327         source: FARM_CONFIG_SOURCE.to_owned(),
    328         field: human_field_name(args.field).to_owned(),
    329         value: human_field_value(args.field, field_value.as_str()).to_owned(),
    330         config: Some(summary_view(
    331             resolved.scope,
    332             path.display().to_string(),
    333             &resolved.document,
    334             account_pubkey,
    335         )),
    336         reason,
    337         actions: farm_update_actions(config, &resolved.document, configured_account.as_ref()),
    338     })
    339 }
    340 
    341 pub fn status(
    342     config: &RuntimeConfig,
    343     args: &FarmScopedArgs,
    344 ) -> Result<FarmStatusView, RuntimeError> {
    345     let scope = scope_from_arg(args.scope);
    346     let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
    347     let path = farm_config::config_path(&config.paths, resolved_scope)?;
    348     let Some(resolved) = farm_config::load(config, Some(resolved_scope))? else {
    349         return Ok(FarmStatusView {
    350             state: "unconfigured".to_owned(),
    351             source: FARM_CONFIG_SOURCE.to_owned(),
    352             scope: resolved_scope.as_str().to_owned(),
    353             path: path.display().to_string(),
    354             config_present: false,
    355             config_valid: false,
    356             account_state: "not_checked".to_owned(),
    357             listing_defaults_state: "missing".to_owned(),
    358             publish_transport: config.publish.transport.as_str().to_owned(),
    359             publish_state: "not_checked".to_owned(),
    360             publish_executable: false,
    361             publish_reason: None,
    362             config: None,
    363             missing: vec!["Farm draft".to_owned()],
    364             reason: Some(format!("no farm config found at {}", path.display())),
    365             actions: vec!["radroots farm create".to_owned()],
    366         });
    367     };
    368 
    369     let account = configured_account(config, &resolved.document.selection.account)?;
    370     let draft_missing = farm_config::missing_fields(&resolved.document);
    371     let account_state = if account.is_some() {
    372         "ready"
    373     } else {
    374         "missing"
    375     };
    376     let listing_defaults_state = if missing_blocks_listing_defaults(draft_missing.as_slice()) {
    377         "missing"
    378     } else {
    379         "ready"
    380     };
    381     let publish = account
    382         .as_ref()
    383         .filter(|_| draft_missing.is_empty())
    384         .map(|account| farm_publish_readiness(config, account))
    385         .unwrap_or_else(FarmPublishReadiness::not_checked);
    386     let state = if account.is_some() && draft_missing.is_empty() && publish.executable {
    387         "ready"
    388     } else {
    389         "unconfigured"
    390     };
    391     let reason = if account.is_none() {
    392         Some(format!(
    393             "farm config account `{}` is not present in the local account store",
    394             resolved.document.selection.account
    395         ))
    396     } else if !draft_missing.is_empty() {
    397         Some("farm draft is missing required fields".to_owned())
    398     } else {
    399         publish.reason.clone()
    400     };
    401     let mut actions = Vec::new();
    402     if account.is_none() {
    403         actions.push("radroots account import <path>".to_owned());
    404         actions.push("radroots farm rebind <selector>".to_owned());
    405     } else if draft_missing.is_empty() {
    406         actions.extend(publish.actions.clone());
    407     } else {
    408         actions.extend(missing_field_actions(draft_missing.as_slice()));
    409     }
    410     let account_pubkey = account
    411         .as_ref()
    412         .map(|account| account.record.public_identity.public_key_hex.as_str());
    413 
    414     Ok(FarmStatusView {
    415         state: state.to_owned(),
    416         source: FARM_CONFIG_SOURCE.to_owned(),
    417         scope: resolved.scope.as_str().to_owned(),
    418         path: resolved.path.display().to_string(),
    419         config_present: true,
    420         config_valid: true,
    421         account_state: account_state.to_owned(),
    422         listing_defaults_state: listing_defaults_state.to_owned(),
    423         publish_transport: config.publish.transport.as_str().to_owned(),
    424         publish_state: publish.state.to_owned(),
    425         publish_executable: publish.executable,
    426         publish_reason: publish.reason,
    427         config: Some(summary_view(
    428             resolved.scope,
    429             resolved.path.display().to_string(),
    430             &resolved.document,
    431             account_pubkey,
    432         )),
    433         missing: if account.is_none() {
    434             vec!["Farm-bound seller account".to_owned()]
    435         } else {
    436             let mut missing = missing_field_labels(draft_missing.as_slice());
    437             missing.extend(publish.missing);
    438             missing
    439         },
    440         reason,
    441         actions,
    442     })
    443 }
    444 
    445 pub fn get(config: &RuntimeConfig, args: &FarmScopedArgs) -> Result<FarmGetView, RuntimeError> {
    446     let scope = scope_from_arg(args.scope);
    447     let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
    448     let path = farm_config::config_path(&config.paths, resolved_scope)?;
    449     let Some(resolved) = farm_config::load(config, Some(resolved_scope))? else {
    450         return Ok(FarmGetView {
    451             state: "unconfigured".to_owned(),
    452             source: FARM_CONFIG_SOURCE.to_owned(),
    453             scope: resolved_scope.as_str().to_owned(),
    454             path: path.display().to_string(),
    455             config_present: false,
    456             document: None,
    457             reason: Some(format!("no farm config found at {}", path.display())),
    458             actions: vec!["radroots farm create".to_owned()],
    459         });
    460     };
    461 
    462     Ok(FarmGetView {
    463         state: "ready".to_owned(),
    464         source: FARM_CONFIG_SOURCE.to_owned(),
    465         scope: resolved.scope.as_str().to_owned(),
    466         path: resolved.path.display().to_string(),
    467         config_present: true,
    468         document: Some(document_view(&resolved.document)),
    469         reason: None,
    470         actions: Vec::new(),
    471     })
    472 }
    473 
    474 #[derive(Debug, Clone)]
    475 struct FarmPublishReadiness {
    476     state: &'static str,
    477     executable: bool,
    478     reason: Option<String>,
    479     missing: Vec<String>,
    480     actions: Vec<String>,
    481 }
    482 
    483 impl FarmPublishReadiness {
    484     fn not_checked() -> Self {
    485         Self {
    486             state: "not_checked",
    487             executable: false,
    488             reason: None,
    489             missing: Vec::new(),
    490             actions: Vec::new(),
    491         }
    492     }
    493 }
    494 
    495 fn farm_publish_readiness(
    496     config: &RuntimeConfig,
    497     account: &AccountRecordView,
    498 ) -> FarmPublishReadiness {
    499     relay_farm_publish_readiness(config, account)
    500 }
    501 
    502 fn relay_farm_publish_readiness(
    503     config: &RuntimeConfig,
    504     account: &AccountRecordView,
    505 ) -> FarmPublishReadiness {
    506     if matches!(config.publish.transport, PublishTransport::DirectNostrRelay)
    507         && config.relay.urls.is_empty()
    508     {
    509         return FarmPublishReadiness {
    510             state: "unconfigured",
    511             executable: false,
    512             reason: Some(
    513                 "direct_nostr_relay farm publish requires at least one configured relay".to_owned(),
    514             ),
    515             missing: vec!["Configured relay".to_owned()],
    516             actions: vec!["radroots --relay wss://relay.example.com farm publish".to_owned()],
    517         };
    518     }
    519 
    520     if matches!(config.signer.backend, SignerBackend::Myc) {
    521         if let Err(error) = validate_configured_signer_for_actor(
    522             config,
    523             Some(account.record.account_id.as_str()),
    524             account.record.public_identity.public_key_hex.as_str(),
    525             "farm seller",
    526         ) {
    527             return FarmPublishReadiness {
    528                 state: "unconfigured",
    529                 executable: false,
    530                 reason: Some(error.to_string()),
    531                 missing: vec!["Remote signer binding".to_owned()],
    532                 actions: vec!["radroots signer status get".to_owned()],
    533             };
    534         }
    535         return FarmPublishReadiness {
    536             state: "ready",
    537             executable: true,
    538             reason: None,
    539             missing: Vec::new(),
    540             actions: Vec::new(),
    541         };
    542     }
    543 
    544     if !account.write_capable {
    545         return FarmPublishReadiness {
    546             state: "unconfigured",
    547             executable: false,
    548             reason: Some(
    549                 account::AccountRuntimeFailure::watch_only(&account.record.account_id).to_string(),
    550             ),
    551             missing: vec!["Write-capable farm-bound seller account".to_owned()],
    552             actions: vec![format!(
    553                 "radroots account attach-secret {} <path>",
    554                 account.record.account_id
    555             )],
    556         };
    557     }
    558 
    559     FarmPublishReadiness {
    560         state: "ready",
    561         executable: true,
    562         reason: None,
    563         missing: Vec::new(),
    564         actions: vec!["radroots farm publish".to_owned()],
    565     }
    566 }
    567 
    568 pub fn publish(
    569     config: &RuntimeConfig,
    570     args: &FarmPublishArgs,
    571 ) -> Result<FarmPublishView, CliSdkAdapterError> {
    572     let scope = scope_from_arg(args.scope);
    573     let resolved_scope = farm_config::resolve_scope(&config.paths, scope)?;
    574     let path = farm_config::config_path(&config.paths, resolved_scope)?;
    575     let Some(resolved) = farm_config::load(config, Some(resolved_scope))? else {
    576         return Ok(missing_publish_view(
    577             config,
    578             resolved_scope,
    579             path.display().to_string(),
    580             args,
    581             format!("no farm config found at {}", path.display()),
    582             vec!["Farm draft".to_owned()],
    583             vec!["radroots farm create".to_owned()],
    584             config.output.dry_run,
    585             false,
    586             String::new(),
    587             String::new(),
    588             String::new(),
    589         ));
    590     };
    591 
    592     let Some(account) = configured_account(config, &resolved.document.selection.account)? else {
    593         return Ok(missing_publish_view(
    594             config,
    595             resolved.scope,
    596             resolved.path.display().to_string(),
    597             args,
    598             format!(
    599                 "farm config account `{}` is not present in the local account store",
    600                 resolved.document.selection.account
    601             ),
    602             vec!["Farm-bound seller account".to_owned()],
    603             vec![
    604                 "radroots account import <path>".to_owned(),
    605                 "radroots farm rebind <selector>".to_owned(),
    606             ],
    607             config.output.dry_run,
    608             true,
    609             resolved.document.selection.account.clone(),
    610             String::new(),
    611             resolved.document.selection.farm_d_tag.clone(),
    612         ));
    613     };
    614     let draft_missing = farm_config::missing_fields(&resolved.document);
    615     if !draft_missing.is_empty() {
    616         return Ok(missing_publish_view(
    617             config,
    618             resolved.scope,
    619             resolved.path.display().to_string(),
    620             args,
    621             "farm draft is missing required fields".to_owned(),
    622             missing_field_labels(draft_missing.as_slice()),
    623             missing_field_actions(draft_missing.as_slice()),
    624             config.output.dry_run,
    625             true,
    626             resolved.document.selection.account.clone(),
    627             account.record.public_identity.public_key_hex.clone(),
    628             resolved.document.selection.farm_d_tag.clone(),
    629         ));
    630     }
    631     let account_pubkey = account.record.public_identity.public_key_hex.clone();
    632     let previews = build_publish_previews(&resolved.document, account_pubkey.as_str())?;
    633     let profile_idempotency_key = component_idempotency_key(args, "profile")?;
    634     let farm_idempotency_key = component_idempotency_key(args, "farm")?;
    635 
    636     publish_via_sdk(
    637         config,
    638         args,
    639         resolved,
    640         account_pubkey,
    641         previews,
    642         profile_idempotency_key,
    643         farm_idempotency_key,
    644     )
    645 }
    646 
    647 fn publish_via_sdk(
    648     config: &RuntimeConfig,
    649     args: &FarmPublishArgs,
    650     mut resolved: ResolvedFarmConfig,
    651     account_pubkey: String,
    652     previews: FarmPublishPreviews,
    653     profile_idempotency_key: Option<String>,
    654     farm_idempotency_key: Option<String>,
    655 ) -> Result<FarmPublishView, CliSdkAdapterError> {
    656     let input = sdk_farm_publish_input(&resolved, account_pubkey.as_str())?;
    657     if config.output.dry_run {
    658         if let Err(error) = validate_configured_signer_for_actor(
    659             config,
    660             Some(resolved.document.selection.account.as_str()),
    661             account_pubkey.as_str(),
    662             "farm seller",
    663         ) {
    664             let binding_error = ActorWriteBindingError::from_runtime(error);
    665             return match binding_error {
    666                 ActorWriteBindingError::Account(failure) => Err(RuntimeError::from(failure).into()),
    667                 error => Ok(binding_error_publish_view(
    668                     config,
    669                     args,
    670                     &resolved,
    671                     &account_pubkey,
    672                     previews,
    673                     profile_idempotency_key,
    674                     farm_idempotency_key,
    675                     error,
    676                 )),
    677             };
    678         }
    679 
    680         let session = CliSdkSession::connect_memory(config)?;
    681         let plan = session
    682             .sdk()
    683             .farms()
    684             .prepare_publish(FarmPreparePublishRequest::new(input.actor, input.farm))?;
    685         return Ok(sdk_prepared_publish_view(
    686             config,
    687             args,
    688             &resolved,
    689             account_pubkey.as_str(),
    690             previews,
    691             profile_idempotency_key,
    692             farm_idempotency_key,
    693             plan,
    694         ));
    695     }
    696 
    697     let session = CliSdkSession::connect_for_actor(
    698         config,
    699         Some(resolved.document.selection.account.as_str()),
    700         account_pubkey.as_str(),
    701         "farm seller",
    702     )?;
    703     let mut request =
    704         FarmEnqueuePublishRequest::new(input.actor, input.farm, sdk_relay_target_policy(config));
    705     if let Some(idempotency_key) = farm_idempotency_key.as_deref() {
    706         request = request.try_with_idempotency_key(idempotency_key)?;
    707     }
    708     let enqueue = session.block_on(session.sdk().farms().enqueue_publish(request))?;
    709     let push = session.block_on(
    710         session.sdk().sync().push_outbox(
    711             PushOutboxRequest::new()
    712                 .with_limit(1)
    713                 .with_relay_url_policy(sdk_relay_url_policy(config)),
    714         ),
    715     )?;
    716     let view = sdk_enqueued_publish_view(
    717         config,
    718         args,
    719         &resolved,
    720         account_pubkey.as_str(),
    721         previews,
    722         profile_idempotency_key,
    723         farm_idempotency_key,
    724         &enqueue,
    725         &push,
    726     );
    727     if view.farm.state == "published" {
    728         persist_farm_publication(
    729             config,
    730             &mut resolved,
    731             enqueue.signed_event_id.as_str().to_owned(),
    732         )?;
    733     }
    734     Ok(view)
    735 }
    736 
    737 #[derive(Debug, Clone)]
    738 struct SdkFarmPublishInput {
    739     actor: RadrootsActorContext,
    740     farm: RadrootsFarm,
    741 }
    742 
    743 #[derive(Debug, Clone)]
    744 struct FarmPublishPreviews {
    745     profile: FarmPublishEventDraft,
    746 }
    747 
    748 #[derive(Debug, Clone)]
    749 struct FarmPublishEventDraft {
    750     event: FarmPublishEventView,
    751 }
    752 
    753 fn missing_publish_view(
    754     config: &RuntimeConfig,
    755     scope: FarmConfigScope,
    756     path: String,
    757     args: &FarmPublishArgs,
    758     reason: String,
    759     missing: Vec<String>,
    760     actions: Vec<String>,
    761     dry_run: bool,
    762     config_present: bool,
    763     seller_account_id: String,
    764     seller_pubkey: String,
    765     farm_d_tag: String,
    766 ) -> FarmPublishView {
    767     FarmPublishView {
    768         state: "unconfigured".to_owned(),
    769         source: farm_write_source(config).to_owned(),
    770         scope: scope.as_str().to_owned(),
    771         path,
    772         config_present,
    773         dry_run,
    774         seller_account_id,
    775         seller_pubkey,
    776         seller_actor_source: FARM_SELLER_ACTOR_SOURCE.to_owned(),
    777         farm_d_tag,
    778         profile: not_submitted_component(
    779             profile_publish_rpc_method(config),
    780             KIND_PROFILE,
    781             args,
    782             None,
    783             None,
    784         ),
    785         farm: not_submitted_component(farm_publish_rpc_method(config), KIND_FARM, args, None, None),
    786         local_replica: Vec::new(),
    787         missing,
    788         reason: Some(reason),
    789         actions,
    790     }
    791 }
    792 
    793 fn base_publish_view(
    794     state: &str,
    795     config: &RuntimeConfig,
    796     _args: &FarmPublishArgs,
    797     resolved: &ResolvedFarmConfig,
    798     account_pubkey: &str,
    799     profile: FarmPublishComponentView,
    800     farm: FarmPublishComponentView,
    801     reason: Option<String>,
    802     actions: Vec<String>,
    803 ) -> FarmPublishView {
    804     FarmPublishView {
    805         state: state.to_owned(),
    806         source: farm_write_source(config).to_owned(),
    807         scope: resolved.scope.as_str().to_owned(),
    808         path: resolved.path.display().to_string(),
    809         config_present: true,
    810         dry_run: config.output.dry_run,
    811         seller_account_id: resolved.document.selection.account.clone(),
    812         seller_pubkey: account_pubkey.to_owned(),
    813         seller_actor_source: FARM_SELLER_ACTOR_SOURCE.to_owned(),
    814         farm_d_tag: resolved.document.selection.farm_d_tag.clone(),
    815         profile,
    816         farm,
    817         local_replica: Vec::new(),
    818         missing: Vec::new(),
    819         reason,
    820         actions,
    821     }
    822 }
    823 
    824 fn build_publish_previews(
    825     document: &FarmConfigDocument,
    826     account_pubkey: &str,
    827 ) -> Result<FarmPublishPreviews, RuntimeError> {
    828     let profile_parts =
    829         to_wire_parts_with_profile_type(&document.profile, Some(RadrootsProfileType::Farm))
    830             .map_err(|error| RuntimeError::Config(format!("invalid farm profile: {error}")))?;
    831 
    832     Ok(FarmPublishPreviews {
    833         profile: FarmPublishEventDraft {
    834             event: FarmPublishEventView {
    835                 kind: profile_parts.kind,
    836                 author: account_pubkey.to_owned(),
    837                 content: profile_parts.content.clone(),
    838                 tags: profile_parts.tags.clone(),
    839                 event_id: None,
    840                 event_addr: None,
    841             },
    842         },
    843     })
    844 }
    845 
    846 fn component_idempotency_key(
    847     args: &FarmPublishArgs,
    848     component: &str,
    849 ) -> Result<Option<String>, RuntimeError> {
    850     args.idempotency_key
    851         .as_deref()
    852         .map(|value| {
    853             required_text(value, "idempotency_key").map(|key| format!("{key}:{component}"))
    854         })
    855         .transpose()
    856 }
    857 
    858 fn preview_component(
    859     rpc_method: &str,
    860     event_kind: u32,
    861     idempotency_key: Option<String>,
    862     args: &FarmPublishArgs,
    863     event: Option<FarmPublishEventView>,
    864 ) -> FarmPublishComponentView {
    865     FarmPublishComponentView {
    866         state: if event.is_some() {
    867             "not_submitted".to_owned()
    868         } else {
    869             "unconfigured".to_owned()
    870         },
    871         rpc_method: rpc_method.to_owned(),
    872         event_kind,
    873         deduplicated: false,
    874         target_relays: Vec::new(),
    875         connected_relays: Vec::new(),
    876         acknowledged_relays: Vec::new(),
    877         failed_relays: Vec::new(),
    878         job_id: None,
    879         job_status: None,
    880         signer_mode: None,
    881         event_id: None,
    882         event_addr: event.as_ref().and_then(|event| event.event_addr.clone()),
    883         idempotency_key: idempotency_key.clone(),
    884         reason: Some("not submitted".to_owned()),
    885         job: None,
    886         event: args.print_event.then_some(event).flatten(),
    887     }
    888 }
    889 
    890 fn not_submitted_component(
    891     rpc_method: &str,
    892     event_kind: u32,
    893     args: &FarmPublishArgs,
    894     idempotency_key: Option<String>,
    895     event: Option<FarmPublishEventView>,
    896 ) -> FarmPublishComponentView {
    897     preview_component(rpc_method, event_kind, idempotency_key, args, event)
    898 }
    899 
    900 fn binding_error_publish_view(
    901     config: &RuntimeConfig,
    902     args: &FarmPublishArgs,
    903     resolved: &ResolvedFarmConfig,
    904     account_pubkey: &str,
    905     previews: FarmPublishPreviews,
    906     profile_idempotency_key: Option<String>,
    907     farm_idempotency_key: Option<String>,
    908     error: ActorWriteBindingError,
    909 ) -> FarmPublishView {
    910     let reason = error.reason();
    911     let state = "unconfigured".to_owned();
    912     let actions = vec!["run radroots signer status get".to_owned()];
    913     base_publish_view(
    914         state.as_str(),
    915         config,
    916         args,
    917         resolved,
    918         account_pubkey,
    919         FarmPublishComponentView {
    920             state: state.clone(),
    921             reason: Some(reason.clone()),
    922             ..profile_not_submitted_component(
    923                 profile_idempotency_key,
    924                 args,
    925                 Some(previews.profile.event),
    926             )
    927         },
    928         FarmPublishComponentView {
    929             state: state.clone(),
    930             reason: Some(reason.clone()),
    931             ..preview_component(
    932                 farm_publish_rpc_method(config),
    933                 KIND_FARM,
    934                 farm_idempotency_key,
    935                 args,
    936                 None,
    937             )
    938         },
    939         Some(reason),
    940         actions,
    941     )
    942 }
    943 
    944 fn sdk_farm_publish_input(
    945     resolved: &ResolvedFarmConfig,
    946     account_pubkey: &str,
    947 ) -> Result<SdkFarmPublishInput, RuntimeError> {
    948     let actor = RadrootsActorContext::local_account(
    949         account_pubkey,
    950         resolved.document.selection.account.clone(),
    951         [RadrootsActorRole::Farmer],
    952     )
    953     .map_err(|error| RuntimeError::Config(format!("invalid farm SDK actor: {error}")))?;
    954     Ok(SdkFarmPublishInput {
    955         actor,
    956         farm: resolved.document.farm.clone(),
    957     })
    958 }
    959 
    960 fn sdk_prepared_publish_view(
    961     config: &RuntimeConfig,
    962     args: &FarmPublishArgs,
    963     resolved: &ResolvedFarmConfig,
    964     account_pubkey: &str,
    965     previews: FarmPublishPreviews,
    966     profile_idempotency_key: Option<String>,
    967     farm_idempotency_key: Option<String>,
    968     plan: FarmPublishPlan,
    969 ) -> FarmPublishView {
    970     base_publish_view(
    971         "dry_run",
    972         config,
    973         args,
    974         resolved,
    975         account_pubkey,
    976         profile_not_submitted_component(
    977             profile_idempotency_key,
    978             args,
    979             Some(previews.profile.event),
    980         ),
    981         FarmPublishComponentView {
    982             state: "not_submitted".to_owned(),
    983             reason: Some("dry run requested; SDK enqueue and relay push skipped".to_owned()),
    984             signer_mode: Some(config.signer.backend.as_str().to_owned()),
    985             event_id: Some(plan.expected_event_id.as_str().to_owned()),
    986             event_addr: Some(plan.farm_addr.as_str().to_owned()),
    987             event: args.print_event.then_some(sdk_plan_event_view(&plan)),
    988             ..preview_component(
    989                 farm_publish_rpc_method(config),
    990                 KIND_FARM,
    991                 farm_idempotency_key,
    992                 args,
    993                 None,
    994             )
    995         },
    996         Some("dry run requested; SDK enqueue and relay push skipped".to_owned()),
    997         vec!["radroots farm publish".to_owned()],
    998     )
    999 }
   1000 
   1001 fn sdk_enqueued_publish_view(
   1002     config: &RuntimeConfig,
   1003     args: &FarmPublishArgs,
   1004     resolved: &ResolvedFarmConfig,
   1005     account_pubkey: &str,
   1006     previews: FarmPublishPreviews,
   1007     profile_idempotency_key: Option<String>,
   1008     farm_idempotency_key: Option<String>,
   1009     enqueue: &FarmEnqueueReceipt,
   1010     push: &PushOutboxReceipt,
   1011 ) -> FarmPublishView {
   1012     let push_event = sdk_push_event_for_farm(enqueue, push);
   1013     let state = sdk_publish_state(push_event);
   1014     let view_state = state.clone();
   1015     let reason = sdk_publish_reason(push_event);
   1016     base_publish_view(
   1017         view_state.as_str(),
   1018         config,
   1019         args,
   1020         resolved,
   1021         account_pubkey,
   1022         profile_not_submitted_component(
   1023             profile_idempotency_key,
   1024             args,
   1025             Some(previews.profile.event),
   1026         ),
   1027         FarmPublishComponentView {
   1028             state,
   1029             deduplicated: matches!(enqueue.state, SdkMutationState::AlreadyQueued),
   1030             target_relays: push_event
   1031                 .map(sdk_push_target_relays)
   1032                 .unwrap_or_else(|| config.relay.urls.clone()),
   1033             connected_relays: push_event
   1034                 .map(sdk_push_connected_relays)
   1035                 .unwrap_or_default(),
   1036             acknowledged_relays: push_event
   1037                 .map(sdk_push_acknowledged_relays)
   1038                 .unwrap_or_default(),
   1039             failed_relays: push_event.map(sdk_push_failed_relays).unwrap_or_default(),
   1040             signer_mode: Some(config.signer.backend.as_str().to_owned()),
   1041             event_id: Some(enqueue.signed_event_id.as_str().to_owned()),
   1042             event_addr: Some(enqueue.farm_addr.as_str().to_owned()),
   1043             idempotency_key: farm_idempotency_key,
   1044             reason: sdk_publish_reason(push_event),
   1045             ..preview_component(farm_publish_rpc_method(config), KIND_FARM, None, args, None)
   1046         },
   1047         reason,
   1048         sdk_publish_actions(push_event),
   1049     )
   1050 }
   1051 
   1052 fn sdk_plan_event_view(plan: &FarmPublishPlan) -> FarmPublishEventView {
   1053     FarmPublishEventView {
   1054         kind: plan.frozen_draft.kind,
   1055         author: plan.frozen_draft.expected_pubkey.clone(),
   1056         content: plan.frozen_draft.content.clone(),
   1057         tags: plan.frozen_draft.tags.clone(),
   1058         event_id: Some(plan.expected_event_id.as_str().to_owned()),
   1059         event_addr: Some(plan.farm_addr.as_str().to_owned()),
   1060     }
   1061 }
   1062 
   1063 fn sdk_push_event_for_farm<'a>(
   1064     enqueue: &FarmEnqueueReceipt,
   1065     push: &'a PushOutboxReceipt,
   1066 ) -> Option<&'a PushOutboxEventReceipt> {
   1067     push.events
   1068         .iter()
   1069         .find(|event| event.event_id == enqueue.signed_event_id)
   1070 }
   1071 
   1072 fn sdk_publish_state(push_event: Option<&PushOutboxEventReceipt>) -> String {
   1073     match push_event.map(|event| event.final_state) {
   1074         Some(PushOutboxEventState::Published) => "published",
   1075         Some(PushOutboxEventState::PublishRetryable | PushOutboxEventState::FailedTerminal) => {
   1076             "unavailable"
   1077         }
   1078         Some(_) | None => "queued",
   1079     }
   1080     .to_owned()
   1081 }
   1082 
   1083 fn sdk_publish_reason(push_event: Option<&PushOutboxEventReceipt>) -> Option<String> {
   1084     match push_event.map(|event| event.final_state) {
   1085         Some(PushOutboxEventState::Published) => None,
   1086         Some(PushOutboxEventState::PublishRetryable) => Some(
   1087             "SDK relay publish did not reach accepted quorum; outbox event remains retryable"
   1088                 .to_owned(),
   1089         ),
   1090         Some(PushOutboxEventState::FailedTerminal) => {
   1091             Some("SDK relay publish failed terminally".to_owned())
   1092         }
   1093         Some(state) => Some(format!("SDK relay push left event in state `{state:?}`")),
   1094         None => Some(
   1095             "farm publish queued in SDK outbox; no ready SDK outbox event was pushed".to_owned(),
   1096         ),
   1097     }
   1098 }
   1099 
   1100 fn sdk_publish_actions(push_event: Option<&PushOutboxEventReceipt>) -> Vec<String> {
   1101     if !matches!(
   1102         push_event.map(|event| event.final_state),
   1103         Some(PushOutboxEventState::Published)
   1104     ) {
   1105         return vec!["radroots sync push".to_owned()];
   1106     }
   1107     Vec::new()
   1108 }
   1109 
   1110 fn sdk_push_target_relays(event: &PushOutboxEventReceipt) -> Vec<String> {
   1111     event
   1112         .relays
   1113         .iter()
   1114         .map(|relay| relay.relay_url.clone())
   1115         .collect()
   1116 }
   1117 
   1118 fn sdk_push_connected_relays(event: &PushOutboxEventReceipt) -> Vec<String> {
   1119     event
   1120         .relays
   1121         .iter()
   1122         .filter(|relay| relay.attempted)
   1123         .map(|relay| relay.relay_url.clone())
   1124         .collect()
   1125 }
   1126 
   1127 fn sdk_push_acknowledged_relays(event: &PushOutboxEventReceipt) -> Vec<String> {
   1128     event
   1129         .relays
   1130         .iter()
   1131         .filter(|relay| {
   1132             matches!(
   1133                 relay.outcome_kind,
   1134                 PushOutboxRelayOutcomeKind::Accepted
   1135                     | PushOutboxRelayOutcomeKind::DuplicateAccepted
   1136             )
   1137         })
   1138         .map(|relay| relay.relay_url.clone())
   1139         .collect()
   1140 }
   1141 
   1142 fn sdk_push_failed_relays(event: &PushOutboxEventReceipt) -> Vec<RelayFailureView> {
   1143     event
   1144         .relays
   1145         .iter()
   1146         .filter(|relay| {
   1147             !matches!(
   1148                 relay.outcome_kind,
   1149                 PushOutboxRelayOutcomeKind::Accepted
   1150                     | PushOutboxRelayOutcomeKind::DuplicateAccepted
   1151             )
   1152         })
   1153         .map(|relay| RelayFailureView {
   1154             relay: relay.relay_url.clone(),
   1155             reason: relay
   1156                 .message
   1157                 .clone()
   1158                 .unwrap_or_else(|| sdk_relay_outcome_kind(relay.outcome_kind).to_owned()),
   1159         })
   1160         .collect()
   1161 }
   1162 
   1163 fn sdk_relay_outcome_kind(kind: PushOutboxRelayOutcomeKind) -> &'static str {
   1164     match kind {
   1165         PushOutboxRelayOutcomeKind::Accepted => "accepted",
   1166         PushOutboxRelayOutcomeKind::DuplicateAccepted => "duplicate_accepted",
   1167         PushOutboxRelayOutcomeKind::Blocked => "blocked",
   1168         PushOutboxRelayOutcomeKind::RateLimited => "rate_limited",
   1169         PushOutboxRelayOutcomeKind::Invalid => "invalid",
   1170         PushOutboxRelayOutcomeKind::PowRequired => "pow_required",
   1171         PushOutboxRelayOutcomeKind::Restricted => "restricted",
   1172         PushOutboxRelayOutcomeKind::AuthRequired => "auth_required",
   1173         PushOutboxRelayOutcomeKind::Error => "error",
   1174         PushOutboxRelayOutcomeKind::Timeout => "timeout",
   1175         PushOutboxRelayOutcomeKind::ConnectionFailed => "connection_failed",
   1176         PushOutboxRelayOutcomeKind::Unknown => "unknown",
   1177         _ => "unknown",
   1178     }
   1179 }
   1180 
   1181 fn profile_not_submitted_component(
   1182     idempotency_key: Option<String>,
   1183     args: &FarmPublishArgs,
   1184     event: Option<FarmPublishEventView>,
   1185 ) -> FarmPublishComponentView {
   1186     FarmPublishComponentView {
   1187         reason: Some(SDK_PROFILE_NOT_SUBMITTED_REASON.to_owned()),
   1188         ..preview_component(
   1189             SDK_PROFILE_NOT_SUBMITTED_METHOD,
   1190             KIND_PROFILE,
   1191             idempotency_key,
   1192             args,
   1193             event,
   1194         )
   1195     }
   1196 }
   1197 
   1198 fn persist_farm_publication(
   1199     config: &RuntimeConfig,
   1200     resolved: &mut ResolvedFarmConfig,
   1201     event_id: String,
   1202 ) -> Result<(), RuntimeError> {
   1203     persist_publication(config, resolved, None, Some(event_id))
   1204 }
   1205 
   1206 fn persist_publication(
   1207     config: &RuntimeConfig,
   1208     resolved: &mut ResolvedFarmConfig,
   1209     profile_event_id: Option<String>,
   1210     farm_event_id: Option<String>,
   1211 ) -> Result<(), RuntimeError> {
   1212     let published_at = now_unix();
   1213     if let Some(event_id) = profile_event_id.and_then(|value| non_empty(value.as_str())) {
   1214         resolved.document.publication.profile_event_id = Some(event_id);
   1215         resolved.document.publication.profile_published_at = Some(published_at);
   1216     }
   1217     if let Some(event_id) = farm_event_id.and_then(|value| non_empty(value.as_str())) {
   1218         resolved.document.publication.farm_event_id = Some(event_id);
   1219         resolved.document.publication.farm_published_at = Some(published_at);
   1220     }
   1221     farm_config::write(&config.paths, resolved.scope, &resolved.document)?;
   1222     Ok(())
   1223 }
   1224 
   1225 fn farm_write_source(config: &RuntimeConfig) -> &'static str {
   1226     let _ = config;
   1227     SDK_FARM_WRITE_SOURCE
   1228 }
   1229 
   1230 fn profile_publish_rpc_method(config: &RuntimeConfig) -> &'static str {
   1231     let _ = config;
   1232     SDK_PROFILE_NOT_SUBMITTED_METHOD
   1233 }
   1234 
   1235 fn farm_publish_rpc_method(config: &RuntimeConfig) -> &'static str {
   1236     let _ = config;
   1237     SDK_FARM_PUBLISH_METHOD
   1238 }
   1239 
   1240 fn selected_account_for_draft(
   1241     config: &RuntimeConfig,
   1242 ) -> Result<Option<AccountRecordView>, RuntimeError> {
   1243     account::resolve_account(config)
   1244 }
   1245 
   1246 fn missing_selected_account_setup_view() -> FarmSetupView {
   1247     FarmSetupView {
   1248         state: "unconfigured".to_owned(),
   1249         source: FARM_CONFIG_SOURCE.to_owned(),
   1250         config: None,
   1251         reason: Some("choose or create an account before setting up your farm".to_owned()),
   1252         actions: vec!["radroots account create".to_owned()],
   1253     }
   1254 }
   1255 
   1256 fn init_document(
   1257     scope: FarmConfigScope,
   1258     account: &AccountRecordView,
   1259     existing: Option<&ResolvedFarmConfig>,
   1260     args: &FarmCreateArgs,
   1261 ) -> Result<FarmConfigDocument, RuntimeError> {
   1262     let existing_document = existing.map(|resolved| &resolved.document);
   1263     if let Some(document) = existing_document
   1264         && document.selection.account != account.record.account_id.to_string()
   1265     {
   1266         let message = format!(
   1267             "account mismatch: farm config is bound to seller account `{}`; use `radroots farm rebind {}` to change the farm-bound seller account",
   1268             document.selection.account, account.record.account_id
   1269         );
   1270         return Err(account::AccountRuntimeFailure::mismatch_with_detail(
   1271             message,
   1272             json!({
   1273                 "seller_actor_source": FARM_SELLER_ACTOR_SOURCE,
   1274                 "farm_bound_seller_account_id": document.selection.account,
   1275                 "attempted_seller_account_id": account.record.account_id.to_string(),
   1276                 "actions": [format!("radroots farm rebind {}", account.record.account_id)],
   1277             }),
   1278         )
   1279         .into());
   1280     }
   1281     let farm_d_tag = match args.farm_d_tag.as_deref() {
   1282         Some(value) => required_d_tag(value, "farm_d_tag")?,
   1283         None => existing_document
   1284             .map(|document| document.farm.d_tag.clone())
   1285             .unwrap_or_else(generate_d_tag),
   1286     };
   1287     let existing_name = existing_name(existing_document);
   1288     let existing_location = existing_location_primary(existing_document);
   1289     let existing_city = existing_city(existing_document);
   1290     let existing_region = existing_region(existing_document);
   1291     let existing_country = existing_country(existing_document);
   1292     let existing_delivery = existing_delivery_method(existing_document);
   1293     let name = optional_arg_or_existing(args.name.as_ref(), existing_name.as_ref())
   1294         .or_else(|| draft_name_from_account(account))
   1295         .unwrap_or_default();
   1296     let display_name = optional_arg_or_existing(
   1297         args.display_name.as_ref(),
   1298         existing_document.and_then(|document| document.profile.display_name.as_ref()),
   1299     )
   1300     .or_else(|| non_empty(name.as_str()));
   1301     let about = optional_arg_or_existing(
   1302         args.about.as_ref(),
   1303         existing_document.and_then(|document| document.profile.about.as_ref()),
   1304     );
   1305     let website = optional_arg_or_existing(
   1306         args.website.as_ref(),
   1307         existing_document.and_then(|document| document.profile.website.as_ref()),
   1308     );
   1309     let picture = optional_arg_or_existing(
   1310         args.picture.as_ref(),
   1311         existing_document.and_then(|document| document.profile.picture.as_ref()),
   1312     );
   1313     let banner = optional_arg_or_existing(
   1314         args.banner.as_ref(),
   1315         existing_document.and_then(|document| document.profile.banner.as_ref()),
   1316     );
   1317     let location_primary =
   1318         optional_arg_or_existing(args.location.as_ref(), existing_location.as_ref())
   1319             .unwrap_or_default();
   1320     let city = optional_arg_or_existing(args.city.as_ref(), existing_city.as_ref());
   1321     let region = optional_arg_or_existing(args.region.as_ref(), existing_region.as_ref());
   1322     let country = optional_arg_or_existing(args.country.as_ref(), existing_country.as_ref());
   1323     let delivery_method =
   1324         optional_arg_or_existing(args.delivery_method.as_ref(), existing_delivery.as_ref())
   1325             .unwrap_or_default();
   1326     let publication = publication_for_document(existing_document, account, farm_d_tag.as_str());
   1327 
   1328     Ok(FarmConfigDocument {
   1329         version: SUPPORTED_FARM_CONFIG_VERSION,
   1330         selection: FarmConfigSelection {
   1331             scope,
   1332             account: account.record.account_id.to_string(),
   1333             farm_d_tag: farm_d_tag.clone(),
   1334         },
   1335         profile: RadrootsProfile {
   1336             name: name.clone(),
   1337             display_name,
   1338             nip05: None,
   1339             about: about.clone(),
   1340             website: website.clone(),
   1341             picture: picture.clone(),
   1342             banner: banner.clone(),
   1343             lud06: None,
   1344             lud16: None,
   1345             bot: None,
   1346         },
   1347         farm: RadrootsFarm {
   1348             d_tag: farm_d_tag,
   1349             name,
   1350             about,
   1351             website,
   1352             picture,
   1353             banner,
   1354             location: Some(RadrootsFarmLocation {
   1355                 primary: non_empty(location_primary.as_str()),
   1356                 city: city.clone(),
   1357                 region: region.clone(),
   1358                 country: country.clone(),
   1359                 gcs: None,
   1360             }),
   1361             tags: None,
   1362         },
   1363         listing_defaults: FarmListingDefaults {
   1364             delivery_method,
   1365             location: RadrootsListingLocation {
   1366                 primary: location_primary,
   1367                 city,
   1368                 region,
   1369                 country,
   1370                 lat: None,
   1371                 lng: None,
   1372                 geohash: None,
   1373             },
   1374         },
   1375         publication,
   1376     })
   1377 }
   1378 
   1379 fn save_draft_view(
   1380     state: &str,
   1381     scope: FarmConfigScope,
   1382     account: &AccountRecordView,
   1383     document: &FarmConfigDocument,
   1384     reason: Option<String>,
   1385     actions: Vec<String>,
   1386     config: &RuntimeConfig,
   1387 ) -> Result<FarmSetupView, RuntimeError> {
   1388     let written_path = farm_config::write(&config.paths, scope, document)?;
   1389     append_farm_local_work(
   1390         config,
   1391         scope,
   1392         written_path.display().to_string(),
   1393         document,
   1394         Some(account.record.public_identity.public_key_hex.as_str()),
   1395     )?;
   1396     Ok(FarmSetupView {
   1397         state: state.to_owned(),
   1398         source: FARM_CONFIG_SOURCE.to_owned(),
   1399         config: Some(summary_view(
   1400             scope,
   1401             written_path.display().to_string(),
   1402             document,
   1403             Some(account.record.public_identity.public_key_hex.as_str()),
   1404         )),
   1405         reason,
   1406         actions,
   1407     })
   1408 }
   1409 
   1410 fn append_farm_local_work(
   1411     config: &RuntimeConfig,
   1412     scope: FarmConfigScope,
   1413     path: String,
   1414     document: &FarmConfigDocument,
   1415     owner_pubkey: Option<&str>,
   1416 ) -> Result<(), RuntimeError> {
   1417     let payload = json!({
   1418         "record_kind": "farm_config_v1",
   1419         "scope": scope.as_str(),
   1420         "path": path,
   1421         "document": document,
   1422     });
   1423     let subject = format!("farm:{}", document.selection.farm_d_tag);
   1424     append_local_work(
   1425         config,
   1426         subject.as_str(),
   1427         Some(document.selection.account.clone()),
   1428         owner_pubkey.map(str::to_owned),
   1429         Some(document.selection.farm_d_tag.clone()),
   1430         None,
   1431         payload,
   1432     )?;
   1433     Ok(())
   1434 }
   1435 
   1436 fn farm_update_actions(
   1437     config: &RuntimeConfig,
   1438     document: &FarmConfigDocument,
   1439     account: Option<&AccountRecordView>,
   1440 ) -> Vec<String> {
   1441     farm_setup_actions(config, document, account)
   1442 }
   1443 
   1444 fn farm_setup_actions(
   1445     config: &RuntimeConfig,
   1446     document: &FarmConfigDocument,
   1447     account: Option<&AccountRecordView>,
   1448 ) -> Vec<String> {
   1449     let mut actions = vec!["radroots farm readiness check".to_owned()];
   1450     if account.is_none() {
   1451         actions.extend(farm_bound_seller_recovery_actions());
   1452         return actions;
   1453     }
   1454     if farm_config::missing_fields(document).is_empty()
   1455         && account
   1456             .map(|account| farm_publish_readiness(config, account).executable)
   1457             .unwrap_or(false)
   1458     {
   1459         actions.push("radroots farm publish".to_owned());
   1460     }
   1461     actions
   1462 }
   1463 
   1464 fn missing_farm_bound_seller_reason(account_id: &str) -> String {
   1465     format!("farm-bound seller account `{account_id}` is not present in the local account store")
   1466 }
   1467 
   1468 fn farm_bound_seller_recovery_actions() -> Vec<String> {
   1469     vec![
   1470         "radroots account import <path>".to_owned(),
   1471         "radroots farm rebind <selector>".to_owned(),
   1472     ]
   1473 }
   1474 
   1475 fn account_recovery_actions() -> Vec<String> {
   1476     vec![
   1477         "radroots account import <path>".to_owned(),
   1478         "radroots account create".to_owned(),
   1479     ]
   1480 }
   1481 
   1482 fn missing_blocks_listing_defaults(missing: &[FarmMissingField]) -> bool {
   1483     missing.iter().any(|field| {
   1484         matches!(
   1485             field,
   1486             FarmMissingField::Location | FarmMissingField::Delivery
   1487         )
   1488     })
   1489 }
   1490 
   1491 fn missing_field_labels(missing: &[FarmMissingField]) -> Vec<String> {
   1492     missing
   1493         .iter()
   1494         .map(|field| field.label().to_owned())
   1495         .collect()
   1496 }
   1497 
   1498 fn missing_field_actions(missing: &[FarmMissingField]) -> Vec<String> {
   1499     let mut actions = Vec::new();
   1500     for field in missing {
   1501         match field {
   1502             FarmMissingField::Name => {
   1503                 push_action(&mut actions, "radroots farm set name \"La Huerta Farm\"");
   1504             }
   1505             FarmMissingField::Location => {
   1506                 push_action(
   1507                     &mut actions,
   1508                     "radroots farm set location \"San Francisco, CA\"",
   1509                 );
   1510             }
   1511             FarmMissingField::Delivery => {
   1512                 push_action(&mut actions, "radroots farm set delivery pickup");
   1513             }
   1514             FarmMissingField::Country => {
   1515                 push_action(&mut actions, "radroots farm set country US");
   1516             }
   1517         }
   1518     }
   1519     actions
   1520 }
   1521 
   1522 fn push_action(actions: &mut Vec<String>, action: &str) {
   1523     if !actions.iter().any(|existing| existing == action) {
   1524         actions.push(action.to_owned());
   1525     }
   1526 }
   1527 
   1528 fn human_field_name(field: FarmFieldArg) -> &'static str {
   1529     match field {
   1530         FarmFieldArg::Name => "Name",
   1531         FarmFieldArg::DisplayName => "Display name",
   1532         FarmFieldArg::About => "About",
   1533         FarmFieldArg::Website => "Website",
   1534         FarmFieldArg::Picture => "Picture",
   1535         FarmFieldArg::Banner => "Banner",
   1536         FarmFieldArg::Location => "Location",
   1537         FarmFieldArg::City => "City",
   1538         FarmFieldArg::Region => "Region",
   1539         FarmFieldArg::Country => "Country",
   1540         FarmFieldArg::Delivery => "Delivery",
   1541     }
   1542 }
   1543 
   1544 fn human_field_value(field: FarmFieldArg, value: &str) -> String {
   1545     match field {
   1546         FarmFieldArg::Delivery => humanize_delivery_method(value),
   1547         _ => value.to_owned(),
   1548     }
   1549 }
   1550 
   1551 fn apply_field_update(
   1552     document: &mut FarmConfigDocument,
   1553     field: FarmFieldArg,
   1554     value: &str,
   1555 ) -> Result<(), RuntimeError> {
   1556     let value = required_text(value, "farm set value")?;
   1557     match field {
   1558         FarmFieldArg::Name => {
   1559             document.profile.name = value.clone();
   1560             document.farm.name = value;
   1561         }
   1562         FarmFieldArg::DisplayName => {
   1563             document.profile.display_name = Some(value);
   1564         }
   1565         FarmFieldArg::About => {
   1566             document.profile.about = Some(value.clone());
   1567             document.farm.about = Some(value);
   1568         }
   1569         FarmFieldArg::Website => {
   1570             document.profile.website = Some(value.clone());
   1571             document.farm.website = Some(value);
   1572         }
   1573         FarmFieldArg::Picture => {
   1574             document.profile.picture = Some(value.clone());
   1575             document.farm.picture = Some(value);
   1576         }
   1577         FarmFieldArg::Banner => {
   1578             document.profile.banner = Some(value.clone());
   1579             document.farm.banner = Some(value);
   1580         }
   1581         FarmFieldArg::Location => {
   1582             document.listing_defaults.location.primary = value.clone();
   1583             ensure_farm_location(document).primary = Some(value);
   1584         }
   1585         FarmFieldArg::City => {
   1586             document.listing_defaults.location.city = Some(value.clone());
   1587             ensure_farm_location(document).city = Some(value);
   1588         }
   1589         FarmFieldArg::Region => {
   1590             document.listing_defaults.location.region = Some(value.clone());
   1591             ensure_farm_location(document).region = Some(value);
   1592         }
   1593         FarmFieldArg::Country => {
   1594             document.listing_defaults.location.country = Some(value.clone());
   1595             ensure_farm_location(document).country = Some(value);
   1596         }
   1597         FarmFieldArg::Delivery => {
   1598             document.listing_defaults.delivery_method = value;
   1599         }
   1600     }
   1601     Ok(())
   1602 }
   1603 
   1604 fn ensure_farm_location(document: &mut FarmConfigDocument) -> &mut RadrootsFarmLocation {
   1605     let primary = non_empty(document.listing_defaults.location.primary.as_str());
   1606     let city = document.listing_defaults.location.city.clone();
   1607     let region = document.listing_defaults.location.region.clone();
   1608     let country = document.listing_defaults.location.country.clone();
   1609     document
   1610         .farm
   1611         .location
   1612         .get_or_insert_with(|| RadrootsFarmLocation {
   1613             primary,
   1614             city,
   1615             region,
   1616             country,
   1617             gcs: None,
   1618         })
   1619 }
   1620 
   1621 fn publication_for_document(
   1622     existing_document: Option<&FarmConfigDocument>,
   1623     account: &AccountRecordView,
   1624     farm_d_tag: &str,
   1625 ) -> FarmPublicationStatus {
   1626     existing_document
   1627         .filter(|document| {
   1628             document.farm.d_tag == farm_d_tag
   1629                 && document.selection.account == account.record.account_id.as_str()
   1630         })
   1631         .map(|document| document.publication.clone())
   1632         .unwrap_or_default()
   1633 }
   1634 
   1635 fn configured_account(
   1636     config: &RuntimeConfig,
   1637     account_id: &str,
   1638 ) -> Result<Option<AccountRecordView>, RuntimeError> {
   1639     let snapshot = account::snapshot(config)?;
   1640     Ok(snapshot
   1641         .accounts
   1642         .into_iter()
   1643         .find(|account| account.record.account_id.as_str() == account_id))
   1644 }
   1645 
   1646 fn summary_view(
   1647     scope: FarmConfigScope,
   1648     path: String,
   1649     document: &FarmConfigDocument,
   1650     account_pubkey: Option<&str>,
   1651 ) -> FarmConfigSummaryView {
   1652     FarmConfigSummaryView {
   1653         scope: scope.as_str().to_owned(),
   1654         path,
   1655         seller_account_id: document.selection.account.clone(),
   1656         seller_pubkey: account_pubkey.map(str::to_owned),
   1657         seller_actor_source: FARM_SELLER_ACTOR_SOURCE.to_owned(),
   1658         farm_d_tag: document.selection.farm_d_tag.clone(),
   1659         name: resolved_name(document).unwrap_or_default(),
   1660         location_primary: resolved_location_primary(document),
   1661         delivery_method: resolved_delivery_method(document).unwrap_or_default(),
   1662         publication: publication_view(&document.publication),
   1663     }
   1664 }
   1665 
   1666 fn document_view(document: &FarmConfigDocument) -> FarmConfigDocumentView {
   1667     FarmConfigDocumentView {
   1668         selection: FarmSelectionView {
   1669             scope: document.selection.scope.as_str().to_owned(),
   1670             seller_account_id: document.selection.account.clone(),
   1671             farm_d_tag: document.selection.farm_d_tag.clone(),
   1672         },
   1673         profile: document.profile.clone(),
   1674         farm: document.farm.clone(),
   1675         listing_defaults: FarmListingDefaultsView {
   1676             delivery_method: document.listing_defaults.delivery_method.clone(),
   1677             location: document.listing_defaults.location.clone(),
   1678         },
   1679         publication: publication_view(&document.publication),
   1680     }
   1681 }
   1682 
   1683 fn publication_view(publication: &FarmPublicationStatus) -> FarmPublicationView {
   1684     FarmPublicationView {
   1685         profile_state: publish_state(
   1686             publication.profile_event_id.as_deref(),
   1687             publication.profile_published_at,
   1688         )
   1689         .to_owned(),
   1690         farm_state: publish_state(
   1691             publication.farm_event_id.as_deref(),
   1692             publication.farm_published_at,
   1693         )
   1694         .to_owned(),
   1695         profile_event_id: publication.profile_event_id.clone(),
   1696         farm_event_id: publication.farm_event_id.clone(),
   1697         profile_published_at: publication.profile_published_at,
   1698         farm_published_at: publication.farm_published_at,
   1699     }
   1700 }
   1701 
   1702 fn publish_state(event_id: Option<&str>, published_at: Option<u64>) -> &'static str {
   1703     if event_id.is_some_and(|value| !value.trim().is_empty()) || published_at.is_some() {
   1704         "published"
   1705     } else {
   1706         "not_published"
   1707     }
   1708 }
   1709 
   1710 fn scope_from_arg(scope: Option<FarmScopeArg>) -> Option<FarmConfigScope> {
   1711     scope.map(|scope| match scope {
   1712         FarmScopeArg::User => FarmConfigScope::User,
   1713         FarmScopeArg::Workspace => FarmConfigScope::Workspace,
   1714     })
   1715 }
   1716 
   1717 fn required_d_tag(value: &str, field: &str) -> Result<String, RuntimeError> {
   1718     let value = required_text(value, field)?;
   1719     if !is_d_tag_base64url(value.as_str()) {
   1720         return Err(RuntimeError::Config(format!(
   1721             "{field} must be a 22-character base64url identifier"
   1722         )));
   1723     }
   1724     Ok(value)
   1725 }
   1726 
   1727 fn required_text(value: &str, field: &str) -> Result<String, RuntimeError> {
   1728     let trimmed = value.trim();
   1729     if trimmed.is_empty() {
   1730         return Err(RuntimeError::Config(format!("{field} must not be empty")));
   1731     }
   1732     Ok(trimmed.to_owned())
   1733 }
   1734 
   1735 fn optional_arg_or_existing(arg: Option<&String>, existing: Option<&String>) -> Option<String> {
   1736     arg.and_then(|value| non_empty(value.as_str()))
   1737         .or_else(|| existing.and_then(|value| non_empty(value.as_str())))
   1738 }
   1739 
   1740 fn draft_name_from_account(account: &AccountRecordView) -> Option<String> {
   1741     account
   1742         .record
   1743         .label
   1744         .as_deref()
   1745         .and_then(non_empty)
   1746         .or_else(|| non_empty(account.record.account_id.as_str()))
   1747 }
   1748 
   1749 fn existing_name(existing_document: Option<&FarmConfigDocument>) -> Option<String> {
   1750     existing_document.and_then(resolved_name)
   1751 }
   1752 
   1753 fn existing_location_primary(existing_document: Option<&FarmConfigDocument>) -> Option<String> {
   1754     existing_document.and_then(resolved_location_primary)
   1755 }
   1756 
   1757 fn existing_city(existing_document: Option<&FarmConfigDocument>) -> Option<String> {
   1758     existing_document
   1759         .and_then(|document| {
   1760             document
   1761                 .farm
   1762                 .location
   1763                 .as_ref()
   1764                 .and_then(|location| location.city.as_ref())
   1765         })
   1766         .and_then(|value| non_empty(value.as_str()))
   1767         .or_else(|| {
   1768             existing_document
   1769                 .and_then(|document| document.listing_defaults.location.city.as_ref())
   1770                 .and_then(|value| non_empty(value.as_str()))
   1771         })
   1772 }
   1773 
   1774 fn existing_region(existing_document: Option<&FarmConfigDocument>) -> Option<String> {
   1775     existing_document
   1776         .and_then(|document| {
   1777             document
   1778                 .farm
   1779                 .location
   1780                 .as_ref()
   1781                 .and_then(|location| location.region.as_ref())
   1782         })
   1783         .and_then(|value| non_empty(value.as_str()))
   1784         .or_else(|| {
   1785             existing_document
   1786                 .and_then(|document| document.listing_defaults.location.region.as_ref())
   1787                 .and_then(|value| non_empty(value.as_str()))
   1788         })
   1789 }
   1790 
   1791 fn existing_country(existing_document: Option<&FarmConfigDocument>) -> Option<String> {
   1792     existing_document
   1793         .and_then(|document| {
   1794             document
   1795                 .farm
   1796                 .location
   1797                 .as_ref()
   1798                 .and_then(|location| location.country.as_ref())
   1799         })
   1800         .and_then(|value| non_empty(value.as_str()))
   1801         .or_else(|| {
   1802             existing_document
   1803                 .and_then(|document| document.listing_defaults.location.country.as_ref())
   1804                 .and_then(|value| non_empty(value.as_str()))
   1805         })
   1806 }
   1807 
   1808 fn existing_delivery_method(existing_document: Option<&FarmConfigDocument>) -> Option<String> {
   1809     existing_document
   1810         .and_then(|document| non_empty(document.listing_defaults.delivery_method.as_str()))
   1811 }
   1812 
   1813 fn resolved_name(document: &FarmConfigDocument) -> Option<String> {
   1814     non_empty(document.profile.name.as_str()).or_else(|| non_empty(document.farm.name.as_str()))
   1815 }
   1816 
   1817 fn resolved_location_primary(document: &FarmConfigDocument) -> Option<String> {
   1818     non_empty(document.listing_defaults.location.primary.as_str()).or_else(|| {
   1819         document
   1820             .farm
   1821             .location
   1822             .as_ref()
   1823             .and_then(|location| location.primary.as_deref())
   1824             .and_then(non_empty)
   1825     })
   1826 }
   1827 
   1828 fn resolved_delivery_method(document: &FarmConfigDocument) -> Option<String> {
   1829     non_empty(document.listing_defaults.delivery_method.as_str())
   1830 }
   1831 
   1832 fn humanize_delivery_method(value: &str) -> String {
   1833     value
   1834         .split('_')
   1835         .filter(|segment| !segment.is_empty())
   1836         .map(capitalize_ascii_word)
   1837         .collect::<Vec<_>>()
   1838         .join(" ")
   1839 }
   1840 
   1841 fn capitalize_ascii_word(word: &str) -> String {
   1842     let mut chars = word.chars();
   1843     let Some(first) = chars.next() else {
   1844         return String::new();
   1845     };
   1846     let mut rendered = String::new();
   1847     rendered.push(first.to_ascii_uppercase());
   1848     rendered.push_str(chars.as_str());
   1849     rendered
   1850 }
   1851 
   1852 fn non_empty(value: &str) -> Option<String> {
   1853     let trimmed = value.trim();
   1854     if trimmed.is_empty() {
   1855         None
   1856     } else {
   1857         Some(trimmed.to_owned())
   1858     }
   1859 }
   1860 
   1861 fn now_unix() -> u64 {
   1862     SystemTime::now()
   1863         .duration_since(UNIX_EPOCH)
   1864         .map(|duration| duration.as_secs())
   1865         .unwrap_or_default()
   1866 }
   1867 
   1868 fn generate_d_tag() -> String {
   1869     let nanos = SystemTime::now()
   1870         .duration_since(UNIX_EPOCH)
   1871         .map(|duration| duration.as_nanos())
   1872         .unwrap_or_default();
   1873     let counter = D_TAG_COUNTER.fetch_add(1, Ordering::Relaxed) as u128;
   1874     encode_base64url_no_pad((nanos ^ counter).to_be_bytes())
   1875 }
   1876 
   1877 fn encode_base64url_no_pad(bytes: [u8; 16]) -> String {
   1878     const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
   1879     let mut output = String::with_capacity(22);
   1880     let mut index = 0usize;
   1881     while index + 3 <= bytes.len() {
   1882         let block = ((bytes[index] as u32) << 16)
   1883             | ((bytes[index + 1] as u32) << 8)
   1884             | (bytes[index + 2] as u32);
   1885         output.push(ALPHABET[((block >> 18) & 0x3f) as usize] as char);
   1886         output.push(ALPHABET[((block >> 12) & 0x3f) as usize] as char);
   1887         output.push(ALPHABET[((block >> 6) & 0x3f) as usize] as char);
   1888         output.push(ALPHABET[(block & 0x3f) as usize] as char);
   1889         index += 3;
   1890     }
   1891     let remaining = bytes.len() - index;
   1892     if remaining == 1 {
   1893         let block = (bytes[index] as u32) << 16;
   1894         output.push(ALPHABET[((block >> 18) & 0x3f) as usize] as char);
   1895         output.push(ALPHABET[((block >> 12) & 0x3f) as usize] as char);
   1896     } else if remaining == 2 {
   1897         let block = ((bytes[index] as u32) << 16) | ((bytes[index + 1] as u32) << 8);
   1898         output.push(ALPHABET[((block >> 18) & 0x3f) as usize] as char);
   1899         output.push(ALPHABET[((block >> 12) & 0x3f) as usize] as char);
   1900         output.push(ALPHABET[((block >> 6) & 0x3f) as usize] as char);
   1901     }
   1902     output
   1903 }
   1904 
   1905 #[cfg(test)]
   1906 mod tests {
   1907     use super::generate_d_tag;
   1908     use radroots_events_codec::d_tag::is_d_tag_base64url;
   1909 
   1910     #[test]
   1911     fn generated_farm_d_tag_is_valid_base64url() {
   1912         assert!(is_d_tag_base64url(&generate_d_tag()));
   1913     }
   1914 }