source_guards.rs (69621B)
1 use std::{ 2 collections::BTreeSet, 3 fs, 4 path::{Path, PathBuf}, 5 }; 6 7 const ALLOWED_MENU_LITERALS: &[&str] = &["cmd-q", "settings window should open"]; 8 9 const ALLOWED_WINDOW_LITERALS: &[&str] = &[ 10 "", 11 " ", 12 "${dollars}.{cents:02}", 13 "${dollars}.{cents:02} / {}", 14 ", ", 15 "+", 16 "-", 17 "0", 18 "1111111111111111111111111111111111111111111111111111111111111111", 19 "127.0.0.1", 20 "14", 21 "14.5", 22 "2", 23 "2 bags", 24 "2222222222222222222222222222222222222222222222222222222222222222", 25 "3333333333333333333333333333333333333333333333333333333333333333", 26 "../../../platforms/macos/App/Resources/AppIconSource.png", 27 "2026-04-23T15:00:00Z", 28 "6", 29 "6.", 30 "6.5", 31 "6.50", 32 "6.500", 33 " Farm Profile ", 34 "Farm Profile", 35 "Salad mix", 36 "USD", 37 "[::1]", 38 "/tmp/radroots/data/apps/app", 39 "/tmp/radroots/data/apps/app/sdk", 40 "/tmp/radroots/logs/apps/app", 41 "{}.{:02}", 42 "abc", 43 "app.sqlite3", 44 "account-add", 45 "account-open-workspace", 46 "account-log-out", 47 "account-more", 48 "account-profile-change-photo", 49 "account-profile-remove-photo", 50 "account-settings-add-relay", 51 "account-settings-blossom-product-photos", 52 "account-settings-blossom-profile-farm-media", 53 "account-settings-relay-localhost-8080", 54 "account-settings-relay-localhost-8081", 55 "account-settings-relay-menu-localhost-8080", 56 "account-settings-relay-menu-localhost-8081", 57 "account-settings-reset-blossom", 58 "account-settings-reset-relays", 59 "account-settings-save", 60 "account-settings-save-draft", 61 "account-farm-card-scroll", 62 "account-farm-details-tabs", 63 "account-farm-save", 64 "account-farm-save-draft", 65 "account-farm-add-pickup-window", 66 "account-farm-profile-preview", 67 "account-farm-view-profile", 68 "account-scroll", 69 "account-tabs", 70 "account_1", 71 "buyer", 72 "buyer-detail-add-to-cart", 73 "buyer-detail-back", 74 "buyer-detail-confirm-replace", 75 "buyer-detail-keep-current", 76 "buyer-detail-quantity-decrease", 77 "buyer-detail-quantity-increase", 78 "buyer-cart-open-order-review", 79 "buyer-cart-remove-line", 80 "buyer-order-review-back", 81 "buyer-order-review-place-order", 82 "buyer-listing-open", 83 "buyer-order-accept-change", 84 "buyer-order-confirm-replace", 85 "buyer-order-cancel", 86 "buyer-order-detail-back", 87 "buyer-order-keep-current", 88 "buyer-order-keep-order", 89 "buyer-order-repeat-demand", 90 "buyer-orders-retry-coordination", 91 "personal_orders", 92 "buyer.add_to_cart_failed", 93 "buyer.cart_remove_failed", 94 "buyer.order_review_place_failed", 95 "buyer.order_review_save_failed", 96 "buyer.detail_open_failed", 97 "buyer.order_open_failed", 98 "buyer.order_cancel_failed", 99 "buyer.order_coordination_retry_failed", 100 "buyer.order_revision_accept_failed", 101 "buyer.order_revision_decline_failed", 102 "buyer.repeat_demand_failed", 103 "buyer.section_select_failed", 104 "buyer_notice", 105 "bunker://466d7fcae563e5cb09a0d1870bb580344804617879a14949cf22285f1bae3f27?relay=wss%3A%2F%2Frelay.radroots.example", 106 "buyer.fulfillment_filter_update_failed", 107 "buyer.search_query_update_failed", 108 "CARGO_PKG_VERSION", 109 "clock", 110 "configuration", 111 "configure_relay_targets", 112 "customer_labels.txt", 113 "desktop runtime paths should resolve", 114 "desktop runtime roots require HOME for macos", 115 "directory", 116 "disk unavailable", 117 "eggs", 118 "event_store.sqlite", 119 "failed to add buyer product to cart", 120 "failed to open buyer order detail", 121 "failed to place buyer order", 122 "failed to retry buyer order coordination", 123 "failed to remove buyer cart line", 124 "failed to reorder buyer order", 125 "failed to save buyer order review draft", 126 "failed to select buyer section", 127 "failed to open buyer product detail", 128 "failed to update buyer fulfillment filter", 129 "failed to update buyer search query", 130 "failed to add relay `{relay_url}`: {error}", 131 "failed to load farm settings projection", 132 "failed to accept buyer order change", 133 "failed to cancel buyer order", 134 "failed to keep buyer order", 135 "failed to open existing product editor", 136 "failed to open new product editor", 137 "failed to acknowledge reminder", 138 "failed to export pack day", 139 "failed to complete pack day batch print", 140 "failed to complete pack day host handoff", 141 "failed to complete pack day print", 142 "failed to prepare pack day batch print", 143 "failed to prepare pack day host handoff", 144 "failed to prepare pack day print", 145 "failed to open order detail", 146 "failed to route into pack day view", 147 "failed to route into orders view", 148 "failed to save farm settings projection", 149 "failed to save product editor draft", 150 "failed to switch into farm mode", 151 "failed to switch into marketplace mode", 152 "failed to update orders filter", 153 "failed to route into products view", 154 "failed to update product stock", 155 "failed to update products filter", 156 "failed to update products search query", 157 "failed to update products sort", 158 "home-browse-marketplace", 159 "home-connect-signer", 160 "home-connect-signer-submit", 161 "home-continue", 162 "home-farm-setup-continue", 163 "home-farm-setup-delivery", 164 "home-farm-setup-finish", 165 "home-farm-setup-pickup", 166 "home-farm-setup-shipping", 167 "home-farm-setup-start", 168 "home-generate-key", 169 "home-sidebar-account-menu", 170 "home-nav-orders", 171 "home-nav-pack-day", 172 "home-nav-products", 173 "home-nav-today", 174 "home-orders-scroll", 175 "home-pack-day-scroll", 176 "buyer-browse-scroll", 177 "buyer-cart-scroll", 178 "buyer-nav-browse", 179 "buyer-nav-cart", 180 "buyer-nav-orders", 181 "buyer-nav-search", 182 "buyer-order-open", 183 "buyer-orders-scroll", 184 "personal-search-delivery", 185 "personal-search-pickup", 186 "personal-search-shipping", 187 "presented reminder", 188 "buyer-search-scroll", 189 "home-today-open-pack-day", 190 "home-today-order-open", 191 "home-signer-back", 192 "home-signer-source-input", 193 "home-today-open-orders", 194 "home-today-open-products-drafts", 195 "home-today-open-products-low-stock", 196 "home-products-scroll", 197 "home-today-scroll", 198 "http://", 199 "http://localhost:8082", 200 "https://", 201 "today-reminder-chip", 202 "https://auth.example/challenge", 203 "identity", 204 "localhost", 205 "npub1", 206 "npub1qqqqq...qqqqqq", 207 "npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", 208 "npub1sxczr...5lkheq", 209 "npub1sxczrq2dp4jtehcm8mtemj975u5ytf2d7mc6dpuuq3rzkjzr76ls5lkheq", 210 "guest", 211 "finder unavailable", 212 "orders", 213 "orders-reminders", 214 "orders-detail-back", 215 "pack-day-export", 216 "pack-day-open-customer-labels", 217 "pack-day-open-pack-sheet", 218 "pack-day-open-pickup-roster", 219 "pack-day-print-all", 220 "pack-day-print-customer-labels", 221 "pack-day-print-pack-sheet", 222 "pack-day-print-pickup-roster", 223 "pack_day", 224 "pack_day.batch_print_failed", 225 "pack_day.batch_print_prepare_failed", 226 "pack_day.host_handoff_failed", 227 "pack_day.host_handoff_prepare_failed", 228 "pack_day.print_failed", 229 "pack_day.print_prepare_failed", 230 "pack-day-reminders", 231 "pack-day-reveal-bundle", 232 "pack_day.export_failed", 233 "pack_day.route_failed", 234 "orders-filter-all", 235 "orders-filter-completed", 236 "orders-filter-needs-action", 237 "orders-filter-packed", 238 "orders-filter-scheduled", 239 "orders-row-action-review", 240 "orders-row-open", 241 "orders.detail_open_failed", 242 "orders.filter_update_failed", 243 "orders.route_failed", 244 "outbox.sqlite", 245 "preview", 246 "pack_sheet.txt", 247 "pack_sheet.txt, pickup_roster.txt, customer_labels.txt", 248 "pickup_roster.txt", 249 "products", 250 "reminder-banner-action", 251 "reminder-banner-dismiss", 252 "reminders", 253 "reminders.ack_failed", 254 "products-filter-all", 255 "products-filter-archived", 256 "products-filter-drafts", 257 "products-filter-live", 258 "products-filter-need-attention", 259 "products-filter-paused", 260 "products-row-stock-action", 261 "products-row-open", 262 "products-sort-availability", 263 "products-sort-name", 264 "products-sort-price", 265 "products-sort-stock", 266 "products-sort-updated", 267 "products-add-product", 268 "products-editor-availability", 269 "products-editor-close", 270 "products-editor-save", 271 "products-editor-status-archived", 272 "products-editor-status-draft", 273 "products-editor-status-live", 274 "products-editor-status-paused", 275 "products.editor_open_failed", 276 "products.editor_save_failed", 277 "products.new_editor_open_failed", 278 "products-stock-editor-cancel", 279 "products-stock-editor-close", 280 "products-stock-editor-save", 281 "products.filter_update_failed", 282 "products.route_failed", 283 "products.search_query_update_failed", 284 "products.stock_update_failed", 285 "products.sort_update_failed", 286 "discovery url does not contain a remote signer uri", 287 "enter a bunker or discovery url to continue", 288 "enter a bunker or discovery url from the signer; raw nostrconnect client uris are signer-side only", 289 "exports/pack_day/window-1/20260423T150000Z", 290 "invalid discovery url:", 291 "invalid discovery url: relative URL without a base", 292 "invalid remote signer uri:", 293 "invalid remote signer uri: invalid public key", 294 "invalid_relay_url", 295 "a remote signer connection is already pending approval", 296 "raw nostrconnect client uris are signer-side only", 297 "remote signer", 298 "remote signer connection failed: relay refused the request", 299 "remote signer did not respond yet", 300 "retry_startup", 301 "retry_status_refresh", 302 "review_runtime_configuration", 303 "runtime unavailable", 304 "radroots_home_view_{label}_{suffix}", 305 "sign_event:kind:1", 306 "shell", 307 "shell-account-entry", 308 "shell-mode-farm", 309 "shell-mode-marketplace", 310 "shell.switch_farm_failed", 311 "shell.switch_marketplace_failed", 312 "sdk_canonical_stores", 313 "settings", 314 "settings-add-blackout-period", 315 "settings-add-fulfillment-window", 316 "settings-farm-add-pickup", 317 "settings-farm-default-pickup", 318 "settings-farm-remove-pickup", 319 "settings-farm-save", 320 "settings-fulfillment-window-pickup-location", 321 "settings-nav-about", 322 "settings-nav-accounts", 323 "settings-nav-farm", 324 "settings-nav-settings", 325 "settings-panel-scroll", 326 "settings-about-acknowledgements", 327 "settings-about-conflict-action", 328 "settings-about-privacy-policy", 329 "settings-about-refresh-sync", 330 "settings-about-refresh-sync-disabled", 331 "settings-about-report-issue", 332 "settings-about-terms", 333 "settings-account-row", 334 "settings-remove-blackout-period", 335 "settings-remove-fulfillment-window", 336 "settings.farm.load_failed", 337 "settings.farm.save_failed", 338 "settings.about.sync_refresh_failed", 339 "settings.about.conflict_resolution_failed", 340 "settings.account.select_failed", 341 "failed to refresh sync from the about panel", 342 "failed to resolve sync conflict from the about panel", 343 "failed to select account from settings panel", 344 "switch_relays", 345 "startup-title-radroots", 346 "startup-title-starting", 347 "wait_for_sdk_lifecycle", 348 "ws://localhost:8080", 349 "ws://localhost:8081", 350 "wss://relay.example", 351 "wss://relay.radroots.example", 352 "{currency_code} {dollars}.{cents:02}", 353 "{prefix}...{suffix}", 354 "{}, {}", 355 "{}: {}", 356 "{} {} {}.", 357 "{quantity} {unit_label}", 358 "{} {}", 359 ]; 360 361 const REQUIRED_WINDOW_COPY_KEYS: &[&str] = &[ 362 "AppTextKey::AppName", 363 "AppTextKey::HomeHeaderMarketplaceMode", 364 "AppTextKey::HomeHeaderFarmMode", 365 "AppTextKey::HomeHeaderAccountSetupAction", 366 "AppTextKey::HomeHeaderGuestLabel", 367 "AppTextKey::AccountTitle", 368 "AppTextKey::AccountTabProfile", 369 "AppTextKey::AccountTabFarmDetails", 370 "AppTextKey::AccountTabPreferences", 371 "AppTextKey::AccountTabSettings", 372 "AppTextKey::AccountNotImplemented", 373 "AppTextKey::AccountFormSaveAction", 374 "AppTextKey::AccountFormSaveDraftAction", 375 "AppTextKey::AccountFarmDetailsTabProfile", 376 "AppTextKey::AccountFarmDetailsTabLocation", 377 "AppTextKey::AccountFarmDetailsTabOperations", 378 "AppTextKey::AccountFarmDetailsTabFulfilment", 379 "AppTextKey::AccountProfilePersonalDetailsTitle", 380 "AppTextKey::AccountProfilePictureLabel", 381 "AppTextKey::AccountProfileChangePhotoAction", 382 "AppTextKey::AccountProfileRemovePhotoAction", 383 "AppTextKey::AccountProfileFullNameLabel", 384 "AppTextKey::AccountProfileEmailLabel", 385 "AppTextKey::AccountProfilePhoneLabel", 386 "AppTextKey::AccountProfileRoleLabel", 387 "AppTextKey::AccountProfileTimeZoneLabel", 388 "AppTextKey::AccountProfileLanguageLabel", 389 "AppTextKey::AccountProfileFullNameValue", 390 "AppTextKey::AccountProfileEmailValue", 391 "AppTextKey::AccountProfilePhoneValue", 392 "AppTextKey::AccountProfileRoleValue", 393 "AppTextKey::AccountProfileRoleFarmManagerValue", 394 "AppTextKey::AccountProfileRoleTeamMemberValue", 395 "AppTextKey::AccountProfileTimeZoneValue", 396 "AppTextKey::AccountProfileTimeZoneMountainValue", 397 "AppTextKey::AccountProfileTimeZoneEasternValue", 398 "AppTextKey::AccountProfileLanguageValue", 399 "AppTextKey::AccountProfileLanguageFrenchValue", 400 "AppTextKey::AccountProfileLanguageSpanishValue", 401 "AppTextKey::AccountFarmDetailsTitle", 402 "AppTextKey::AccountFarmDetailsFarmProfileTitle", 403 "AppTextKey::AccountFarmDetailsFarmProfileIntro", 404 "AppTextKey::AccountFarmDetailsFarmNameLabel", 405 "AppTextKey::AccountFarmDetailsPublicFarmNameLabel", 406 "AppTextKey::AccountFarmDetailsShortDescriptionLabel", 407 "AppTextKey::AccountFarmDetailsFarmTypeLabel", 408 "AppTextKey::AccountFarmDetailsContactEmailLabel", 409 "AppTextKey::AccountFarmDetailsPublicPhoneLabel", 410 "AppTextKey::AccountFarmDetailsWebsiteLabel", 411 "AppTextKey::AccountFarmDetailsEstablishedYearLabel", 412 "AppTextKey::AccountFarmDetailsAboutFarmLabel", 413 "AppTextKey::AccountFarmDetailsFarmNameValue", 414 "AppTextKey::AccountFarmDetailsPublicFarmNameValue", 415 "AppTextKey::AccountFarmDetailsShortDescriptionValue", 416 "AppTextKey::AccountFarmDetailsContactEmailValue", 417 "AppTextKey::AccountFarmDetailsPublicPhoneValue", 418 "AppTextKey::AccountFarmDetailsWebsiteValue", 419 "AppTextKey::AccountFarmDetailsEstablishedYearValue", 420 "AppTextKey::AccountFarmDetailsAboutFarmValue", 421 "AppTextKey::AccountFarmDetailsFarmLocationValue", 422 "AppTextKey::AccountFarmDetailsSummaryTitle", 423 "AppTextKey::AccountFarmDetailsFarmTypeSummaryLabel", 424 "AppTextKey::AccountFarmDetailsEstablishedSummaryLabel", 425 "AppTextKey::AccountFarmDetailsViewFarmProfileAction", 426 "AppTextKey::AccountFarmDetailsFarmTypeVegetableFarm", 427 "AppTextKey::AccountFarmDetailsFarmTypeFruitOrchard", 428 "AppTextKey::AccountFarmDetailsFarmTypeBerryFarm", 429 "AppTextKey::AccountFarmDetailsFarmTypeHerbFarm", 430 "AppTextKey::AccountFarmDetailsFarmTypeFlowerFarm", 431 "AppTextKey::AccountFarmDetailsFarmTypeMushroomFarm", 432 "AppTextKey::AccountFarmDetailsFarmTypeGrainFieldCropFarm", 433 "AppTextKey::AccountFarmDetailsFarmTypeDairyFarm", 434 "AppTextKey::AccountFarmDetailsFarmTypeEggPoultryFarm", 435 "AppTextKey::AccountFarmDetailsFarmTypeLivestockFarm", 436 "AppTextKey::AccountFarmDetailsFarmTypeHoneyApiary", 437 "AppTextKey::AccountFarmDetailsFarmTypeNurseryPlantFarm", 438 "AppTextKey::AccountFarmDetailsFarmTypeMixedFarm", 439 "AppTextKey::AccountFarmDetailsFarmTypeOther", 440 "AppTextKey::AccountFarmDetailsLocationTitle", 441 "AppTextKey::AccountFarmDetailsLocationIntro", 442 "AppTextKey::AccountFarmDetailsMapNotImplemented", 443 "AppTextKey::AccountFarmDetailsStreetAddressLabel", 444 "AppTextKey::AccountFarmDetailsStreetAddressValue", 445 "AppTextKey::AccountFarmDetailsCityLabel", 446 "AppTextKey::AccountFarmDetailsCityValue", 447 "AppTextKey::AccountFarmDetailsProvinceLabel", 448 "AppTextKey::AccountFarmDetailsProvinceBritishColumbia", 449 "AppTextKey::AccountFarmDetailsProvinceAlberta", 450 "AppTextKey::AccountFarmDetailsPostalCodeLabel", 451 "AppTextKey::AccountFarmDetailsPostalCodeValue", 452 "AppTextKey::AccountFarmDetailsCountryLabel", 453 "AppTextKey::AccountFarmDetailsCountryCanada", 454 "AppTextKey::AccountFarmDetailsCountryUnitedStates", 455 "AppTextKey::AccountFarmDetailsServiceAreaLabel", 456 "AppTextKey::AccountFarmDetailsServiceAreaValue", 457 "AppTextKey::AccountFarmDetailsServiceAreaHelper", 458 "AppTextKey::AccountFarmDetailsExactAddressPublicLabel", 459 "AppTextKey::AccountFarmDetailsExactAddressPublicHelper", 460 "AppTextKey::AccountFarmDetailsLocationPreviewTitle", 461 "AppTextKey::AccountFarmDetailsLocationPreviewHelper", 462 "AppTextKey::AccountFarmDetailsOperatingTitle", 463 "AppTextKey::AccountFarmDetailsOperatingIntro", 464 "AppTextKey::AccountFarmDetailsGrowingPracticesLabel", 465 "AppTextKey::AccountFarmDetailsGrowingPracticeRegenerative", 466 "AppTextKey::AccountFarmDetailsGrowingPracticeOrganic", 467 "AppTextKey::AccountFarmDetailsProductionMethodsLabel", 468 "AppTextKey::AccountFarmDetailsProductionMethodOrganicPractices", 469 "AppTextKey::AccountFarmDetailsProductionMethodNoSpray", 470 "AppTextKey::AccountFarmDetailsSeasonDatesLabel", 471 "AppTextKey::AccountFarmDetailsSeasonStartValue", 472 "AppTextKey::AccountFarmDetailsSeasonEndValue", 473 "AppTextKey::AccountFarmDetailsOrderDaysLabel", 474 "AppTextKey::AccountFarmDetailsOrderDaysSummaryValue", 475 "AppTextKey::AccountFarmDetailsDayMon", 476 "AppTextKey::AccountFarmDetailsDayTue", 477 "AppTextKey::AccountFarmDetailsDayWed", 478 "AppTextKey::AccountFarmDetailsDayThu", 479 "AppTextKey::AccountFarmDetailsDayFri", 480 "AppTextKey::AccountFarmDetailsDaySat", 481 "AppTextKey::AccountFarmDetailsDaySun", 482 "AppTextKey::AccountFarmDetailsAboutProductsLabel", 483 "AppTextKey::AccountFarmDetailsAboutProductsValue", 484 "AppTextKey::AccountFarmDetailsCertificationsTitle", 485 "AppTextKey::AccountFarmDetailsCertificationsHelper", 486 "AppTextKey::AccountFarmDetailsCertificationCertifiedOrganic", 487 "AppTextKey::AccountFarmDetailsCertificationNaturallyGrown", 488 "AppTextKey::AccountFarmDetailsCertificationSmallFamilyFarm", 489 "AppTextKey::AccountFarmDetailsCertificationDeliveryAvailable", 490 "AppTextKey::AccountFarmDetailsCustomerNoteTitle", 491 "AppTextKey::AccountFarmDetailsCustomerNoteHelper", 492 "AppTextKey::AccountFarmDetailsCustomerNoteValue", 493 "AppTextKey::AccountFarmDetailsProfilePreviewTitle", 494 "AppTextKey::AccountFarmDetailsGrowingPracticesSummaryLabel", 495 "AppTextKey::AccountFarmDetailsSeasonSummaryLabel", 496 "AppTextKey::AccountFarmDetailsOrderDaysSummaryLabel", 497 "AppTextKey::AccountFarmDetailsPickupFulfillmentTitle", 498 "AppTextKey::AccountFarmDetailsPickupFulfillmentIntro", 499 "AppTextKey::AccountFarmDetailsFulfillmentModeLabel", 500 "AppTextKey::AccountFarmDetailsFulfillmentPickupOnly", 501 "AppTextKey::AccountFarmDetailsFulfillmentDelivery", 502 "AppTextKey::AccountFarmDetailsFulfillmentBoth", 503 "AppTextKey::AccountFarmDetailsPrimaryPickupLocationLabel", 504 "AppTextKey::AccountFarmDetailsPrimaryPickupLocationTitleValue", 505 "AppTextKey::AccountFarmDetailsPrimaryPickupLocationAddressValue", 506 "AppTextKey::AccountFarmDetailsPickupInstructionsLabel", 507 "AppTextKey::AccountFarmDetailsPickupInstructionsValue", 508 "AppTextKey::AccountFarmDetailsPickupInstructionsHelper", 509 "AppTextKey::AccountFarmDetailsPickupWindowsLabel", 510 "AppTextKey::AccountFarmDetailsPickupWindowDayHeader", 511 "AppTextKey::AccountFarmDetailsPickupWindowStartHeader", 512 "AppTextKey::AccountFarmDetailsPickupWindowEndHeader", 513 "AppTextKey::AccountFarmDetailsPickupWindowWednesday", 514 "AppTextKey::AccountFarmDetailsPickupWindowSaturday", 515 "AppTextKey::AccountFarmDetailsPickupWindowWednesdayStart", 516 "AppTextKey::AccountFarmDetailsPickupWindowWednesdayEnd", 517 "AppTextKey::AccountFarmDetailsPickupWindowSaturdayStart", 518 "AppTextKey::AccountFarmDetailsPickupWindowSaturdayEnd", 519 "AppTextKey::AccountFarmDetailsAddPickupWindowAction", 520 "AppTextKey::AccountFarmDetailsOrderCutoffLabel", 521 "AppTextKey::AccountFarmDetailsOrderCutoffHelper", 522 "AppTextKey::AccountFarmDetailsOrderCutoffNoonValue", 523 "AppTextKey::AccountFarmDetailsDeliveryRadiusTitle", 524 "AppTextKey::AccountFarmDetailsDeliveryRadiusHelper", 525 "AppTextKey::AccountFarmDetailsDeliveryRadiusValue", 526 "AppTextKey::AccountFarmDetailsDeliveryRadiusUnit", 527 "AppTextKey::AccountFarmDetailsDeliveryRadiusNote", 528 "AppTextKey::AccountFarmDetailsCustomerExperienceTitle", 529 "AppTextKey::AccountFarmDetailsCustomerExperienceIntro", 530 "AppTextKey::AccountFarmDetailsCustomerExperiencePickupTitle", 531 "AppTextKey::AccountFarmDetailsCustomerExperienceDeliveryTitle", 532 "AppTextKey::AccountFarmDetailsCustomerExperienceDeliveryBody", 533 "AppTextKey::AccountSettingsTitle", 534 "AppTextKey::AccountSettingsNostrRelaysTitle", 535 "AppTextKey::AccountSettingsNostrRelaysHelper", 536 "AppTextKey::AccountSettingsRelayAccessReadWrite", 537 "AppTextKey::AccountSettingsRelayAccessReadOnly", 538 "AppTextKey::AccountSettingsRelayMenuAbout", 539 "AppTextKey::AccountSettingsRelayMenuView", 540 "AppTextKey::AccountSettingsRemoveRelayAction", 541 "AppTextKey::AccountSettingsRelayMenuCheckConnection", 542 "AppTextKey::AccountSettingsRelayMenuCopy", 543 "AppTextKey::AccountSettingsRelayMenuCopyShortcut", 544 "AppTextKey::AccountSettingsAddRelayLabel", 545 "AppTextKey::AccountSettingsAddRelayPlaceholder", 546 "AppTextKey::AccountSettingsAddRelayAction", 547 "AppTextKey::AccountSettingsResetRelaysAction", 548 "AppTextKey::AccountSettingsDefaultRelaysNote", 549 "AppTextKey::AccountSettingsBlossomServerTitle", 550 "AppTextKey::AccountSettingsBlossomServerHelper", 551 "AppTextKey::AccountSettingsBlossomServerUrlLabel", 552 "AppTextKey::AccountSettingsBlossomProductPhotosLabel", 553 "AppTextKey::AccountSettingsBlossomProfileFarmMediaLabel", 554 "AppTextKey::AccountSettingsResetBlossomServerAction", 555 "AppTextKey::AccountSettingsBlossomConnectionHealthy", 556 "AppTextKey::AccountSettingsBlossomConnectionLocal", 557 "AppTextKey::AccountSettingsBlossomConnectionInvalid", 558 "AppTextKey::AccountSettingsBlossomUploadsAvailable", 559 "AppTextKey::AccountSettingsBlossomUploadsPending", 560 "AppTextKey::AccountSettingsBlossomUploadsUnavailable", 561 "AppTextKey::HomeSetupBackAction", 562 "AppTextKey::HomeSetupBrowseMarketplaceAction", 563 "AppTextKey::HomeSetupConnectSignerAction", 564 "AppTextKey::HomeSetupContinueAction", 565 "AppTextKey::HomeSetupGenerateKeyAction", 566 "AppTextKey::HomeSetupSignerConnectAction", 567 "AppTextKey::HomeSetupSignerSourcePlaceholder", 568 "AppTextKey::HomeSetupSignerReviewTitle", 569 "AppTextKey::HomeSetupSignerSourceLabel", 570 "AppTextKey::HomeSetupSignerSignerLabel", 571 "AppTextKey::HomeSetupSignerRelaysLabel", 572 "AppTextKey::HomeSetupSignerPermissionsLabel", 573 "AppTextKey::HomeSetupSignerConnectingTitle", 574 "AppTextKey::HomeSetupSignerPendingTitle", 575 "AppTextKey::HomeSetupSignerAuthChallengeTitle", 576 "AppTextKey::HomeSetupSignerApprovedTitle", 577 "AppTextKey::HomeSetupIssueUnavailableBody", 578 "AppTextKey::HomeSetupErrorStartupFailed", 579 "AppTextKey::HomeSetupSignerSourceValueBunkerUri", 580 "AppTextKey::HomeSetupSignerSourceValueDiscoveryUrl", 581 "AppTextKey::HomeSetupSignerPermissionSignEventKind1", 582 "AppTextKey::HomeSetupSignerPermissionSwitchRelays", 583 "AppTextKey::HomeSetupSignerPermissionAdditional", 584 "AppTextKey::HomeSetupSignerErrorEnterSource", 585 "AppTextKey::HomeSetupSignerErrorUseSignerUri", 586 "AppTextKey::HomeSetupSignerErrorMissingDiscoveryUri", 587 "AppTextKey::HomeSetupSignerErrorInvalidDiscoveryUrl", 588 "AppTextKey::HomeSetupSignerErrorInvalidRemoteSignerUri", 589 "AppTextKey::HomeSetupSignerErrorPendingApprovalExists", 590 "AppTextKey::HomeSetupSignerErrorConnectionFailed", 591 "AppTextKey::HomeFarmSetupOnboardingTitle", 592 "AppTextKey::HomeFarmSetupOnboardingBody", 593 "AppTextKey::HomeFarmSetupOnboardingAction", 594 "AppTextKey::HomeFarmSetupSectionFarm", 595 "AppTextKey::HomeFarmSetupSectionLocation", 596 "AppTextKey::HomeFarmSetupSectionOrderMethods", 597 "AppTextKey::HomeFarmSetupFieldFarmName", 598 "AppTextKey::HomeFarmSetupFieldLocationOrServiceArea", 599 "AppTextKey::HomeFarmSetupOrderMethodPickup", 600 "AppTextKey::HomeFarmSetupOrderMethodDelivery", 601 "AppTextKey::HomeFarmSetupOrderMethodShipping", 602 "AppTextKey::HomeFarmSetupBlockerAddFarmName", 603 "AppTextKey::HomeFarmSetupBlockerAddLocationOrServiceArea", 604 "AppTextKey::HomeFarmSetupBlockerChooseOrderMethod", 605 "AppTextKey::HomeFarmSetupSaveAutosavesLocally", 606 "AppTextKey::HomeFarmSetupSaveSavedLocally", 607 "AppTextKey::HomeFarmSetupSaveFailedLocally", 608 "AppTextKey::HomeFarmSetupFinishAction", 609 "AppTextKey::HomeFarmSetupContinueAction", 610 "AppTextKey::HomeTodayOpenInProductsAction", 611 "AppTextKey::HomeNavBrowse", 612 "AppTextKey::HomeNavSearch", 613 "AppTextKey::HomeNavCart", 614 "AppTextKey::HomeNavToday", 615 "AppTextKey::HomeNavProducts", 616 "AppTextKey::HomeNavOrders", 617 "AppTextKey::PersonalSearchFiltersTitle", 618 "AppTextKey::PersonalSearchPlaceholder", 619 "AppTextKey::PersonalBrowseEmptyTitle", 620 "AppTextKey::PersonalBrowseEmptyBody", 621 "AppTextKey::PersonalSearchEmptyTitle", 622 "AppTextKey::PersonalSearchEmptyBody", 623 "AppTextKey::PersonalBrowsePlaceholderBody", 624 "AppTextKey::PersonalSearchPlaceholderBody", 625 "AppTextKey::PersonalMarketplaceRefreshFailedNotice", 626 "AppTextKey::PersonalDetailOpenFailedNotice", 627 "AppTextKey::PersonalOrderPlaceFailedNotice", 628 "AppTextKey::PersonalOrderCoordinationFailedNotice", 629 "AppTextKey::PersonalCartPlaceholderBody", 630 "AppTextKey::PersonalOrdersSurfaceBody", 631 "AppTextKey::PersonalOrdersEmptyTitle", 632 "AppTextKey::PersonalOrdersEmptyBody", 633 "AppTextKey::PersonalOrdersListTitle", 634 "AppTextKey::PersonalOrdersDetailTitle", 635 "AppTextKey::PersonalOrdersDetailFarmLabel", 636 "AppTextKey::PersonalOrdersDetailFulfillmentLabel", 637 "AppTextKey::PersonalOrdersDetailTotalLabel", 638 "AppTextKey::PersonalOrdersDetailNoteLabel", 639 "AppTextKey::PersonalOrdersDetailItemsTitle", 640 "AppTextKey::PersonalOrdersActionCancel", 641 "AppTextKey::PersonalOrdersActionAcceptChange", 642 "AppTextKey::PersonalOrdersActionKeepOrder", 643 "AppTextKey::PersonalOrdersRepeatDemandTitle", 644 "AppTextKey::PersonalOrdersRepeatDemandActionEligible", 645 "AppTextKey::PersonalOrdersRepeatDemandActionPartial", 646 "AppTextKey::PersonalOrdersRepeatDemandNotePartialSingle", 647 "AppTextKey::PersonalOrdersRepeatDemandNotePartialMultiple", 648 "AppTextKey::PersonalOrdersRepeatDemandNoteUnavailable", 649 "AppTextKey::PersonalCartSurfaceBody", 650 "AppTextKey::PersonalOrderSummaryTitle", 651 "AppTextKey::PersonalFulfillmentTitle", 652 "AppTextKey::PersonalCartRemoveLineAction", 653 "AppTextKey::PersonalCartReviewOrderAction", 654 "AppTextKey::PersonalCartLineQuantityLabel", 655 "AppTextKey::PersonalCartLineUnitPriceLabel", 656 "AppTextKey::PersonalCartLineTotalLabel", 657 "AppTextKey::PersonalSummaryFarmLabel", 658 "AppTextKey::PersonalSummaryItemsLabel", 659 "AppTextKey::PersonalSummarySubtotalLabel", 660 "AppTextKey::PersonalDetailBackAction", 661 "AppTextKey::PersonalDetailQuantityLabel", 662 "AppTextKey::PersonalDetailAddToCartAction", 663 "AppTextKey::PersonalDetailReplaceCartTitle", 664 "AppTextKey::PersonalDetailReplaceCartBody", 665 "AppTextKey::PersonalDetailReplaceCartAction", 666 "AppTextKey::PersonalDetailKeepCurrentCartAction", 667 "AppTextKey::PersonalOrderReviewTitle", 668 "AppTextKey::PersonalOrderReviewBackAction", 669 "AppTextKey::PersonalOrderReviewContactTitle", 670 "AppTextKey::PersonalOrderReviewFieldName", 671 "AppTextKey::PersonalOrderReviewFieldEmail", 672 "AppTextKey::PersonalOrderReviewFieldPhone", 673 "AppTextKey::PersonalOrderReviewFieldOrderNote", 674 "AppTextKey::PersonalOrderReviewLocalOnlyBody", 675 "AppTextKey::PersonalOrderReviewPlaceOrderAction", 676 "AppTextKey::HomeTodayOpenInOrdersAction", 677 "AppTextKey::HomeTodayOpenInPackDayAction", 678 "AppTextKey::OrdersTitle", 679 "AppTextKey::OrdersFiltersTitle", 680 "AppTextKey::OrdersSummaryTotal", 681 "AppTextKey::OrdersFilterAll", 682 "AppTextKey::OrdersStatusNeedsAction", 683 "AppTextKey::OrdersStatusScheduled", 684 "AppTextKey::OrdersStatusInHandoff", 685 "AppTextKey::OrdersStatusCompleted", 686 "AppTextKey::OrdersTableTitle", 687 "AppTextKey::OrdersColumnOrder", 688 "AppTextKey::OrdersColumnStatus", 689 "AppTextKey::OrdersColumnWindow", 690 "AppTextKey::OrdersColumnPickup", 691 "AppTextKey::OrdersColumnAction", 692 "AppTextKey::OrdersActionReview", 693 "AppTextKey::OrdersEmptyTitle", 694 "AppTextKey::OrdersEmptyBody", 695 "AppTextKey::OrdersEmptyNeedsActionTitle", 696 "AppTextKey::OrdersEmptyNeedsActionBody", 697 "AppTextKey::OrdersDetailTitle", 698 "AppTextKey::OrdersDetailItemsTitle", 699 "AppTextKey::OrdersDetailCustomerLabel", 700 "AppTextKey::OrdersDetailWindowLabel", 701 "AppTextKey::OrdersDetailPickupLabel", 702 "AppTextKey::OrdersDetailTotalLabel", 703 "AppTextKey::TradeValidationReceiptSectionLabel", 704 "AppTextKey::TradeValidationReceiptRecordedAtLabel", 705 "AppTextKey::TradeValidationReceiptResultValid", 706 "AppTextKey::TradeValidationReceiptResultNeedsReview", 707 "AppTextKey::TradeValidationReceiptTypeListingValidation", 708 "AppTextKey::TradeValidationReceiptTypeTradeTransition", 709 "AppTextKey::TradeValidationReceiptTypeInventoryState", 710 "AppTextKey::TradeValidationReceiptTypeStateCheckpoint", 711 "AppTextKey::TradeWorkflowAxisAgreement", 712 "AppTextKey::TradeWorkflowAxisRevision", 713 "AppTextKey::TradeWorkflowAxisInventory", 714 "AppTextKey::TradeWorkflowAxisSource", 715 "AppTextKey::TradeWorkflowAgreementOrdered", 716 "AppTextKey::TradeWorkflowAgreementConfirmed", 717 "AppTextKey::TradeWorkflowAgreementDeclined", 718 "AppTextKey::TradeWorkflowAgreementCancelled", 719 "AppTextKey::TradeWorkflowAgreementNeedsReview", 720 "AppTextKey::TradeWorkflowRevisionNone", 721 "AppTextKey::TradeWorkflowRevisionChangeProposed", 722 "AppTextKey::TradeWorkflowRevisionUpdated", 723 "AppTextKey::TradeWorkflowRevisionKeptAsPlaced", 724 "AppTextKey::TradeWorkflowInventoryAvailable", 725 "AppTextKey::TradeWorkflowInventoryReserved", 726 "AppTextKey::TradeWorkflowInventorySoldOut", 727 "AppTextKey::TradeWorkflowInventoryNeedsReview", 728 "AppTextKey::TradeWorkflowProvenanceApp", 729 "AppTextKey::TradeWorkflowProvenanceCli", 730 "AppTextKey::TradeWorkflowProvenanceRelay", 731 "AppTextKey::TradeWorkflowProvenanceLocalEvents", 732 "AppTextKey::TradeWorkflowProvenanceUnknown", 733 "AppTextKey::OrdersRemindersTitle", 734 "AppTextKey::OrdersReminderLogTitle", 735 "AppTextKey::OrdersReminderLogEmptyBody", 736 "AppTextKey::PackDayTitle", 737 "AppTextKey::PackDayRemindersTitle", 738 "AppTextKey::PackDayWindowSummaryTitle", 739 "AppTextKey::PackDayTotalsTitle", 740 "AppTextKey::PackDayPackListTitle", 741 "AppTextKey::PackDayPickupRosterTitle", 742 "AppTextKey::PackDayEmptyTitle", 743 "AppTextKey::PackDayEmptyBody", 744 "AppTextKey::PackDayExportTitle", 745 "AppTextKey::PackDayExportReadyTitle", 746 "AppTextKey::PackDayExportReadyBody", 747 "AppTextKey::PackDayExportUnavailableTitle", 748 "AppTextKey::PackDayExportUnavailableBody", 749 "AppTextKey::PackDayExportRunningTitle", 750 "AppTextKey::PackDayExportRunningBody", 751 "AppTextKey::PackDayExportSucceededTitle", 752 "AppTextKey::PackDayExportSucceededBody", 753 "AppTextKey::PackDayExportFailedTitle", 754 "AppTextKey::PackDayExportFailedBody", 755 "AppTextKey::PackDayExportAction", 756 "AppTextKey::PackDayExportActionRunning", 757 "AppTextKey::PackDayExportFolderLabel", 758 "AppTextKey::PackDayExportFilesLabel", 759 "AppTextKey::PackDayExportErrorLabel", 760 "AppTextKey::PackDayBatchPrintAction", 761 "AppTextKey::PackDayBatchPrintActionRunning", 762 "AppTextKey::PackDayBatchPrintQueuedTitle", 763 "AppTextKey::PackDayBatchPrintSucceededTitle", 764 "AppTextKey::PackDayBatchPrintFailedTitle", 765 "AppTextKey::PackDayBatchPrintFailedPreflightTitle", 766 "AppTextKey::PackDayBatchPrintFailedQueueLaunchTitle", 767 "AppTextKey::PackDayBatchPrintFailedQueueExitTitle", 768 "AppTextKey::PackDayBatchPrintCustomerLabelsAvery5160OverflowFailedTitle", 769 "AppTextKey::PackDayPrintCustomerLabelsAvery5160OverflowFailedTitle", 770 "AppTextKey::PackDayHostHandoffRevealAction", 771 "AppTextKey::PackDayHostHandoffRevealActionRunning", 772 "AppTextKey::PackDayHostHandoffOpenPackSheetAction", 773 "AppTextKey::PackDayHostHandoffOpenPackSheetActionRunning", 774 "AppTextKey::PackDayHostHandoffOpenPickupRosterAction", 775 "AppTextKey::PackDayHostHandoffOpenPickupRosterActionRunning", 776 "AppTextKey::PackDayHostHandoffOpenCustomerLabelsAction", 777 "AppTextKey::PackDayHostHandoffOpenCustomerLabelsActionRunning", 778 "AppTextKey::PackDayHostHandoffRevealRunningTitle", 779 "AppTextKey::PackDayHostHandoffRevealSucceededTitle", 780 "AppTextKey::PackDayHostHandoffRevealFailedTitle", 781 "AppTextKey::PackDayHostHandoffOpenPackSheetRunningTitle", 782 "AppTextKey::PackDayHostHandoffOpenPackSheetSucceededTitle", 783 "AppTextKey::PackDayHostHandoffOpenPackSheetFailedTitle", 784 "AppTextKey::PackDayHostHandoffOpenPickupRosterRunningTitle", 785 "AppTextKey::PackDayHostHandoffOpenPickupRosterSucceededTitle", 786 "AppTextKey::PackDayHostHandoffOpenPickupRosterFailedTitle", 787 "AppTextKey::PackDayHostHandoffOpenCustomerLabelsRunningTitle", 788 "AppTextKey::PackDayHostHandoffOpenCustomerLabelsSucceededTitle", 789 "AppTextKey::PackDayHostHandoffOpenCustomerLabelsFailedTitle", 790 "AppTextKey::HomeTodayRemindersTitle", 791 "AppTextKey::ReminderDeadlineLabel", 792 "AppTextKey::ReminderUrgencyUpcoming", 793 "AppTextKey::ReminderUrgencyDueSoon", 794 "AppTextKey::ReminderUrgencyOverdue", 795 "AppTextKey::ReminderUrgencyBlocking", 796 "AppTextKey::ReminderPresentationTitle", 797 "AppTextKey::ReminderPresentationDismissAction", 798 "AppTextKey::ReminderDeliveryStateScheduled", 799 "AppTextKey::ReminderDeliveryStatePresented", 800 "AppTextKey::ReminderDeliveryStateAcknowledged", 801 "AppTextKey::ReminderDeliveryStateResolved", 802 "AppTextKey::ProductsTitle", 803 "AppTextKey::ProductsFiltersTitle", 804 "AppTextKey::ProductsSearchPlaceholder", 805 "AppTextKey::ProductsSummaryTotal", 806 "AppTextKey::ProductsSummaryLive", 807 "AppTextKey::ProductsSummaryNeedAttention", 808 "AppTextKey::ProductsSummaryDrafts", 809 "AppTextKey::ProductsFilterAll", 810 "AppTextKey::ProductsFilterLive", 811 "AppTextKey::ProductsFilterDrafts", 812 "AppTextKey::ProductsFilterNeedAttention", 813 "AppTextKey::ProductsFilterPaused", 814 "AppTextKey::ProductsFilterArchived", 815 "AppTextKey::ProductsSortTitle", 816 "AppTextKey::ProductsSortUpdated", 817 "AppTextKey::ProductsSortName", 818 "AppTextKey::ProductsSortAvailability", 819 "AppTextKey::ProductsSortStock", 820 "AppTextKey::ProductsSortPrice", 821 "AppTextKey::ProductsTableTitle", 822 "AppTextKey::ProductsColumnProduct", 823 "AppTextKey::ProductsColumnStatus", 824 "AppTextKey::ProductsColumnAvailability", 825 "AppTextKey::ProductsColumnStock", 826 "AppTextKey::ProductsColumnPrice", 827 "AppTextKey::ProductsColumnUpdated", 828 "AppTextKey::ProductsColumnAction", 829 "AppTextKey::ProductsAddAction", 830 "AppTextKey::ProductsUpdateStockAction", 831 "AppTextKey::ProductsEditorTitle", 832 "AppTextKey::ProductsEditorBody", 833 "AppTextKey::ProductsEditorFieldTitle", 834 "AppTextKey::ProductsEditorFieldSubtitle", 835 "AppTextKey::ProductsEditorFieldCategory", 836 "AppTextKey::ProductsEditorFieldUnit", 837 "AppTextKey::ProductsEditorFieldPrice", 838 "AppTextKey::ProductsEditorFieldStock", 839 "AppTextKey::ProductsEditorFieldAvailability", 840 "AppTextKey::ProductsEditorFieldStatus", 841 "AppTextKey::ProductsEditorAvailabilityEmpty", 842 "AppTextKey::ProductsEditorCloseAction", 843 "AppTextKey::ProductsEditorSaveAction", 844 "AppTextKey::ProductsEditorSaveFailed", 845 "AppTextKey::ProductsEditorPublishQueueFailed", 846 "AppTextKey::ProductsEditorInvalidPrice", 847 "AppTextKey::ProductsEditorInvalidStock", 848 "AppTextKey::ProductsEditorPublishReadinessTitle", 849 "AppTextKey::ProductsEditorReady", 850 "AppTextKey::ProductsEditorBlockerAddProductName", 851 "AppTextKey::ProductsEditorBlockerChooseCategory", 852 "AppTextKey::ProductsEditorBlockerChooseUnit", 853 "AppTextKey::ProductsEditorBlockerSetPrice", 854 "AppTextKey::ProductsEditorBlockerSetStock", 855 "AppTextKey::ProductsEditorBlockerAttachAvailability", 856 "AppTextKey::ProductsUntitledDraft", 857 "AppTextKey::ProductsStockEditorTitle", 858 "AppTextKey::ProductsStockEditorFieldLabel", 859 "AppTextKey::ProductsStockEditorSaveAction", 860 "AppTextKey::ProductsStockEditorCancelAction", 861 "AppTextKey::ProductsStockEditorInvalidQuantity", 862 "AppTextKey::ProductsStockEditorSaveFailed", 863 "AppTextKey::ProductsStockEditorPublishQueueFailed", 864 "AppTextKey::ProductsStatusDraft", 865 "AppTextKey::ProductsStatusLive", 866 "AppTextKey::ProductsStatusPaused", 867 "AppTextKey::ProductsStatusArchived", 868 "AppTextKey::ProductsEmptyTitle", 869 "AppTextKey::ProductsEmptyBody", 870 "AppTextKey::ProductsEmptyNeedAttentionTitle", 871 "AppTextKey::ProductsEmptyNeedAttentionBody", 872 "AppTextKey::SettingsAccountNoSelectionTitle", 873 "AppTextKey::SettingsAccountNoSelectionBody", 874 "AppTextKey::SettingsAccountStatusLoggedOut", 875 "AppTextKey::SettingsAccountActivationInactive", 876 "AppTextKey::SettingsAccountAddAction", 877 "AppTextKey::SettingsAccountLogOutAction", 878 "AppTextKey::SettingsAccountOpenWorkspaceAction", 879 "AppTextKey::SettingsAccountImportFileAction", 880 "AppTextKey::SettingsAccountImportDatabaseAction", 881 "AppTextKey::SettingsAccountConnectRemoteBunkerAction", 882 "AppTextKey::SettingsNavFarm", 883 "AppTextKey::SettingsFarmPanelBody", 884 "AppTextKey::SettingsFarmUnavailableBody", 885 "AppTextKey::SettingsFarmSaveAction", 886 "AppTextKey::SettingsFarmSaveSaved", 887 "AppTextKey::SettingsFarmSavePending", 888 "AppTextKey::SettingsFarmSaveBlocked", 889 "AppTextKey::SettingsFarmSaveFailed", 890 "AppTextKey::SettingsFarmFieldTimezone", 891 "AppTextKey::SettingsFarmFieldCurrency", 892 "AppTextKey::SettingsPickupLocationsSectionLabel", 893 "AppTextKey::SettingsPickupLocationsEmptyBody", 894 "AppTextKey::SettingsPickupLocationsAddAction", 895 "AppTextKey::SettingsPickupLocationsMakeDefaultAction", 896 "AppTextKey::SettingsPickupLocationsDefaultBadge", 897 "AppTextKey::SettingsPickupLocationsRemoveAction", 898 "AppTextKey::SettingsPickupLocationsFieldLabel", 899 "AppTextKey::SettingsPickupLocationsFieldAddress", 900 "AppTextKey::SettingsPickupLocationsFieldDirections", 901 "AppTextKey::SettingsPickupLocationsFieldDefault", 902 "AppTextKey::SettingsSettingsPanelBody", 903 "AppTextKey::SettingsOperatingRulesSectionLabel", 904 "AppTextKey::SettingsOperatingRulesFieldPromiseLeadTime", 905 "AppTextKey::SettingsOperatingRulesFieldSubstitutionPolicy", 906 "AppTextKey::SettingsOperatingRulesInvalidPromiseLeadTime", 907 "AppTextKey::SettingsFulfillmentWindowsSectionLabel", 908 "AppTextKey::SettingsFulfillmentWindowsEmptyBody", 909 "AppTextKey::SettingsFulfillmentWindowsPickupLocationsBody", 910 "AppTextKey::SettingsFulfillmentWindowsAddAction", 911 "AppTextKey::SettingsFulfillmentWindowsRemoveAction", 912 "AppTextKey::SettingsFulfillmentWindowsItemLabel", 913 "AppTextKey::SettingsFulfillmentWindowsFieldLabel", 914 "AppTextKey::SettingsFulfillmentWindowsFieldPickupLocation", 915 "AppTextKey::SettingsFulfillmentWindowsFieldStartsAt", 916 "AppTextKey::SettingsFulfillmentWindowsFieldEndsAt", 917 "AppTextKey::SettingsFulfillmentWindowsFieldOrderCutoff", 918 "AppTextKey::SettingsFulfillmentWindowsValidationCompleteBeforeSave", 919 "AppTextKey::SettingsFulfillmentWindowsValidationChoosePickupLocation", 920 "AppTextKey::SettingsBlackoutPeriodsSectionLabel", 921 "AppTextKey::SettingsBlackoutPeriodsEmptyBody", 922 "AppTextKey::SettingsBlackoutPeriodsAddAction", 923 "AppTextKey::SettingsBlackoutPeriodsRemoveAction", 924 "AppTextKey::SettingsBlackoutPeriodsItemLabel", 925 "AppTextKey::SettingsBlackoutPeriodsFieldLabel", 926 "AppTextKey::SettingsBlackoutPeriodsFieldStartsAt", 927 "AppTextKey::SettingsBlackoutPeriodsFieldEndsAt", 928 "AppTextKey::SettingsBlackoutPeriodsValidationCompleteBeforeSave", 929 "AppTextKey::SettingsReadinessSectionLabel", 930 "AppTextKey::SettingsReadinessFieldMissingProfileBasics", 931 "AppTextKey::SettingsReadinessFieldMissingPickupLocation", 932 "AppTextKey::SettingsReadinessFieldMissingFulfillmentWindow", 933 "AppTextKey::SettingsReadinessFieldMissingOperatingRules", 934 "AppTextKey::SettingsReadinessFieldInvalidTimingConflicts", 935 "AppTextKey::SettingsReadinessFieldFulfillmentWindowEndsBeforeStart", 936 "AppTextKey::SettingsReadinessFieldFulfillmentWindowCutoffAfterStart", 937 "AppTextKey::SettingsReadinessFieldBlackoutPeriodEndsBeforeStart", 938 "AppTextKey::SettingsReadinessFieldBlackoutOverlapsFulfillmentWindow", 939 "AppTextKey::SettingsReadinessReady", 940 "AppTextKey::SettingsAboutCompanyName", 941 "AppTextKey::SettingsAboutVersionLabel", 942 "AppTextKey::SettingsAboutVariantLabel", 943 "AppTextKey::SettingsAboutAcknowledgementsAction", 944 "AppTextKey::SettingsAboutPrivacyPolicyAction", 945 "AppTextKey::SettingsAboutTermsAction", 946 "AppTextKey::SettingsAboutReportIssueAction", 947 "AppTextKey::SettingsAboutCopyrightNotice", 948 "AppTextKey::SettingsAboutTrademarkNotice", 949 "AppTextKey::SettingsAboutStatusSectionLabel", 950 "AppTextKey::SettingsAboutConflictReviewSectionLabel", 951 "AppTextKey::SettingsAboutRuntimeSectionLabel", 952 "AppTextKey::SettingsAboutConflictReviewUnavailable", 953 "AppTextKey::SettingsAboutConflictReviewClear", 954 "AppTextKey::SettingsAboutConflictReviewNeedsAttention", 955 "AppTextKey::SettingsAboutConflictReviewBlocking", 956 "AppTextKey::MetadataSelectedAccount", 957 "AppTextKey::MetadataSyncPendingWriteCount", 958 "AppTextKey::MetadataSyncBlockingConflictCount", 959 ]; 960 961 const FORBIDDEN_LAUNCHER_UI_BYPASS_PATTERNS: &[(&str, &str)] = &[ 962 ( 963 "Button::new(", 964 "launcher code must use radroots_app_ui button primitives", 965 ), 966 ( 967 "Checkbox::new(", 968 "launcher code must use radroots_app_ui checkbox primitives", 969 ), 970 ( 971 "Input::new(", 972 "launcher code must use radroots_app_ui input primitives", 973 ), 974 ( 975 "TextInput::new(", 976 "launcher code must use radroots_app_ui input primitives", 977 ), 978 ( 979 "pub fn app_", 980 "shared app_* helpers belong in radroots_app_ui, not in launcher code", 981 ), 982 ( 983 "fn app_", 984 "shared app_* helpers belong in radroots_app_ui, not in launcher code", 985 ), 986 ]; 987 988 const REMOVED_WINDOW_HELPER_FAMILIES: &[&str] = &[ 989 "fn settings_account_detail_row(", 990 "fn settings_checkbox_row(", 991 "fn settings_text_field(", 992 "fn settings_dynamic_action_button(", 993 "fn settings_inventory_panel(", 994 "fn settings_inventory_field_row(", 995 "fn settings_validation_rows(", 996 "fn home_farm_setup_blocker(", 997 ]; 998 999 const FORBIDDEN_HARDCODED_WORKFLOW_UI_LITERALS: &[&str] = &[ 1000 "Agreement", 1001 "Change", 1002 "Fulfillment", 1003 "Stock", 1004 "Requested", 1005 "Agreed", 1006 "Payment", 1007 "Source", 1008 "Ordered", 1009 "Confirmed", 1010 "Declined", 1011 "Cancelled", 1012 "Completed", 1013 "Needs review", 1014 "No change", 1015 "Change proposed", 1016 "Updated", 1017 "Kept as placed", 1018 "Preparing", 1019 "Ready for pickup", 1020 "Out for delivery", 1021 "Delivered", 1022 "Available", 1023 "Reserved", 1024 "Sold out", 1025 "Not recorded", 1026 "Pending", 1027 "Recorded", 1028 "Settled", 1029 "App", 1030 "CLI", 1031 "Relay", 1032 "Local events", 1033 "Unknown", 1034 ]; 1035 1036 const FORBIDDEN_STALE_SELLER_LIFECYCLE_PATTERNS: &[&str] = &[ 1037 concat!("orders-detail-", "mark-packed"), 1038 concat!("orders-detail-", "mark-completed"), 1039 concat!("orders-row-action-", "mark-packed"), 1040 concat!("orders-row-action-", "mark-completed"), 1041 concat!("orders.", "mark_delivered_failed"), 1042 concat!("OrderPrimaryAction::", "MarkPacked"), 1043 concat!("OrderPrimaryAction::", "MarkCompleted"), 1044 concat!("mark", "_packed"), 1045 concat!("mark", "_completed"), 1046 concat!("AppTextKey::", "OrdersStatus", "Packed"), 1047 concat!("AppTextKey::", "OrdersAction", "MarkPacked"), 1048 concat!("AppTextKey::", "OrdersAction", "MarkCompleted"), 1049 concat!("orders.status.", "packed"), 1050 concat!("orders.action.", "mark_packed"), 1051 concat!("orders.action.", "mark_completed"), 1052 ]; 1053 1054 const FORBIDDEN_PAYMENT_DEFERRAL_COPY_PATTERNS: &[&str] = &[ 1055 "payments are deferred", 1056 "payment is deferred", 1057 "payment deferred", 1058 "payments deferred", 1059 "deferred payment", 1060 "deferred payments", 1061 "checkout unavailable", 1062 "figure it out", 1063 "payment handling outside the app", 1064 "refund outside the app", 1065 "handle any refund outside the app", 1066 "settle outside the app", 1067 ]; 1068 1069 const FORBIDDEN_PAYMENT_ACTION_COPY_TERMS: &[&str] = &[ 1070 "checkout", 1071 "pay", 1072 "refund", 1073 "settlement", 1074 "wallet", 1075 "invoice", 1076 "bank", 1077 "card", 1078 "processor", 1079 "provider", 1080 "payment-provider", 1081 "payment provider", 1082 ]; 1083 1084 const FORBIDDEN_PRODUCTION_EVENT_KIND_LITERALS: &[(&str, &str)] = &[ 1085 ("30340", "KIND_FARM"), 1086 ("30402", "KIND_LISTING"), 1087 ("30403", "KIND_LISTING_DRAFT"), 1088 ("3422", "KIND_ORDER_REQUEST"), 1089 ("3423", "KIND_ORDER_DECISION"), 1090 ("3424", "KIND_ORDER_REVISION_PROPOSAL"), 1091 ("3425", "KIND_ORDER_REVISION_DECISION"), 1092 ("3426", "KIND_TRADE_QUESTION"), 1093 ("3427", "KIND_TRADE_ANSWER"), 1094 ("3428", "KIND_TRADE_DISCOUNT_REQUEST"), 1095 ("3429", "KIND_TRADE_DISCOUNT_OFFER"), 1096 ("3430", "KIND_TRADE_DISCOUNT_ACCEPT"), 1097 ("3431", "RESERVED_ORDER_KIND_3431"), 1098 ("3432", "KIND_ORDER_CANCELLATION"), 1099 ("3433", "KIND_ORDER_FULFILLMENT_UPDATE"), 1100 ("3434", "KIND_ORDER_RECEIPT"), 1101 ("3435", "KIND_ORDER_PAYMENT_RECORD"), 1102 ("3436", "KIND_ORDER_SETTLEMENT_DECISION"), 1103 ("3440", "KIND_TRADE_VALIDATION_RECEIPT"), 1104 ]; 1105 1106 struct SdkBoundaryForbiddenPattern { 1107 pattern: &'static str, 1108 reason: &'static str, 1109 } 1110 1111 struct LegacySdkBoundaryAllowlistEntry { 1112 path: &'static str, 1113 pattern: &'static str, 1114 owner: &'static str, 1115 reason: &'static str, 1116 removal_condition: &'static str, 1117 } 1118 1119 const TEST_MODULE_SENTINEL: &str = "\n#[cfg(test)]\nmod tests {"; 1120 1121 const STRICT_SDK_BOUNDARY_FORBIDDEN_PATTERNS: &[SdkBoundaryForbiddenPattern] = &[ 1122 SdkBoundaryForbiddenPattern { 1123 pattern: "SdkDirectRelayAppSyncTransport", 1124 reason: "app production sources must use AppSdkRuntime instead of the legacy direct relay sync transport", 1125 }, 1126 SdkBoundaryForbiddenPattern { 1127 pattern: "RadrootsSdkClient", 1128 reason: "app production sources must use the long-lived RadrootsSdk runtime boundary", 1129 }, 1130 SdkBoundaryForbiddenPattern { 1131 pattern: "RadrootsSdkConfig", 1132 reason: "app production sources must use AppSdkConfig-derived runtime construction", 1133 }, 1134 SdkBoundaryForbiddenPattern { 1135 pattern: "SdkTransportMode::RelayDirect", 1136 reason: "app production sources must not configure direct relay publish transport", 1137 }, 1138 SdkBoundaryForbiddenPattern { 1139 pattern: "SignerConfig::LocalIdentity", 1140 reason: "app production sources must not configure local direct-publish signing", 1141 }, 1142 SdkBoundaryForbiddenPattern { 1143 pattern: "PendingSyncOperation::from_publish_payload", 1144 reason: "app production sources must not enqueue legacy app publish payloads", 1145 }, 1146 SdkBoundaryForbiddenPattern { 1147 pattern: ".enqueue_pending_operation(", 1148 reason: "app production sources must not mutate the legacy app outbox", 1149 }, 1150 SdkBoundaryForbiddenPattern { 1151 pattern: "INSERT INTO local_outbox", 1152 reason: "app production sources must not write legacy local outbox rows", 1153 }, 1154 SdkBoundaryForbiddenPattern { 1155 pattern: "UPDATE local_outbox", 1156 reason: "app production sources must not mutate legacy local outbox rows", 1157 }, 1158 SdkBoundaryForbiddenPattern { 1159 pattern: "DELETE FROM local_outbox", 1160 reason: "app production sources must not delete legacy local outbox rows", 1161 }, 1162 SdkBoundaryForbiddenPattern { 1163 pattern: "RadrootsOutbox", 1164 reason: "canonical SDK outbox access belongs inside the SDK crate", 1165 }, 1166 SdkBoundaryForbiddenPattern { 1167 pattern: "enqueue_signed_operation", 1168 reason: "canonical SDK outbox writes must go through SDK APIs", 1169 }, 1170 SdkBoundaryForbiddenPattern { 1171 pattern: "claim_ready_events", 1172 reason: "canonical SDK outbox push claims must go through SDK APIs", 1173 }, 1174 SdkBoundaryForbiddenPattern { 1175 pattern: "connected_client_from_identity", 1176 reason: "app production sources must not connect relay clients directly for publish", 1177 }, 1178 SdkBoundaryForbiddenPattern { 1179 pattern: "publish_signed_event", 1180 reason: "app production sources must not publish signed events directly", 1181 }, 1182 SdkBoundaryForbiddenPattern { 1183 pattern: "radroots_nostr_build_event", 1184 reason: "app production sources must not build protocol events outside SDK-owned publish APIs", 1185 }, 1186 SdkBoundaryForbiddenPattern { 1187 pattern: "RadrootsIdentity::from_secret_key_str", 1188 reason: "app production sources must not parse direct signing keys", 1189 }, 1190 SdkBoundaryForbiddenPattern { 1191 pattern: "RawSecretKey", 1192 reason: "app production sources must not import raw signing-key material", 1193 }, 1194 SdkBoundaryForbiddenPattern { 1195 pattern: "EncryptedSecretKey", 1196 reason: "app production sources must not import encrypted signing-key material", 1197 }, 1198 SdkBoundaryForbiddenPattern { 1199 pattern: "publish_with_identity", 1200 reason: "app production sources must not call legacy direct SDK publish APIs", 1201 }, 1202 SdkBoundaryForbiddenPattern { 1203 pattern: "publish_draft_with_identity", 1204 reason: "app production sources must not encode legacy direct SDK publish targets", 1205 }, 1206 SdkBoundaryForbiddenPattern { 1207 pattern: "publish_order_request_with_identity", 1208 reason: "app production sources must not call legacy direct SDK order publish APIs", 1209 }, 1210 SdkBoundaryForbiddenPattern { 1211 pattern: "publish_order_decision_with_identity", 1212 reason: "app production sources must not call legacy direct SDK order publish APIs", 1213 }, 1214 SdkBoundaryForbiddenPattern { 1215 pattern: "publish_order_revision_proposal_with_identity", 1216 reason: "app production sources must not call legacy direct SDK order publish APIs", 1217 }, 1218 SdkBoundaryForbiddenPattern { 1219 pattern: "publish_order_revision_decision_with_identity", 1220 reason: "app production sources must not call legacy direct SDK order publish APIs", 1221 }, 1222 SdkBoundaryForbiddenPattern { 1223 pattern: "publish_order_cancellation_with_identity", 1224 reason: "app production sources must not call legacy direct SDK order publish APIs", 1225 }, 1226 SdkBoundaryForbiddenPattern { 1227 pattern: "publish_fulfillment_update_with_identity", 1228 reason: "app production sources must not call legacy direct SDK fulfillment publish APIs", 1229 }, 1230 SdkBoundaryForbiddenPattern { 1231 pattern: "publish_buyer_receipt_with_identity", 1232 reason: "app production sources must not call legacy direct SDK receipt publish APIs", 1233 }, 1234 ]; 1235 1236 const LEGACY_SDK_BOUNDARY_ALLOWLIST: &[LegacySdkBoundaryAllowlistEntry] = &[ 1237 LegacySdkBoundaryAllowlistEntry { 1238 path: "crates/desktop/src/accounts.rs", 1239 pattern: "RadrootsIdentity::from_secret_key_str", 1240 owner: "rpv1-app-sdk-hardening.04", 1241 reason: "desktop account import still accepts local raw secret-key material for account bootstrap", 1242 removal_condition: "remove when local account import is mediated by protected signer adapters instead of raw key parsing", 1243 }, 1244 LegacySdkBoundaryAllowlistEntry { 1245 path: "crates/desktop/src/accounts.rs", 1246 pattern: "RawSecretKey", 1247 owner: "rpv1-app-sdk-hardening.04", 1248 reason: "desktop account import still exposes a raw secret-key import mode for account bootstrap", 1249 removal_condition: "remove when local account import is mediated by protected signer adapters instead of raw key import modes", 1250 }, 1251 LegacySdkBoundaryAllowlistEntry { 1252 path: "crates/desktop/src/accounts.rs", 1253 pattern: "EncryptedSecretKey", 1254 owner: "rpv1-app-sdk-hardening.04", 1255 reason: "desktop account import still exposes an encrypted secret-key import mode for account bootstrap", 1256 removal_condition: "remove when local account import is mediated by protected signer adapters instead of secret-key import modes", 1257 }, 1258 LegacySdkBoundaryAllowlistEntry { 1259 path: "crates/signer/src/protocol.rs", 1260 pattern: "RadrootsIdentity::from_secret_key_str", 1261 owner: "rpv1-sdksign.5", 1262 reason: "remote signer startup custody still reloads the NIP-46 client identity before shared protocol transport execution", 1263 removal_condition: "remove when startup remote signer custody stores client identities through protected signer-session APIs", 1264 }, 1265 LegacySdkBoundaryAllowlistEntry { 1266 path: "crates/store/src/lib.rs", 1267 pattern: ".enqueue_pending_operation(", 1268 owner: "rpv1-app-sdk-refactor.07", 1269 reason: "store facade still accepts legacy app local_outbox publish operations for deferred workflows", 1270 removal_condition: "remove when app local_outbox enqueue is replaced by SDK canonical outbox enqueue APIs", 1271 }, 1272 LegacySdkBoundaryAllowlistEntry { 1273 path: "crates/store/src/sync.rs", 1274 pattern: "INSERT INTO local_outbox", 1275 owner: "rpv1-app-sdk-refactor.07", 1276 reason: "store sync implementation still writes legacy app local_outbox rows for deferred workflows", 1277 removal_condition: "remove when app local_outbox storage is retired after SDK canonical outbox migration", 1278 }, 1279 LegacySdkBoundaryAllowlistEntry { 1280 path: "crates/store/src/sync.rs", 1281 pattern: "UPDATE local_outbox", 1282 owner: "rpv1-app-sdk-refactor.07", 1283 reason: "store sync implementation still updates legacy app local_outbox rows for deferred workflows", 1284 removal_condition: "remove when app local_outbox storage is retired after SDK canonical outbox migration", 1285 }, 1286 LegacySdkBoundaryAllowlistEntry { 1287 path: "crates/store/src/sync.rs", 1288 pattern: "DELETE FROM local_outbox", 1289 owner: "rpv1-app-sdk-refactor.07", 1290 reason: "store sync implementation still deletes legacy app local_outbox rows for deferred workflows", 1291 removal_condition: "remove when app local_outbox storage is retired after SDK canonical outbox migration", 1292 }, 1293 ]; 1294 1295 #[test] 1296 fn desktop_menu_source_uses_localized_copy_paths() { 1297 assert_eq!( 1298 extract_string_literals(include_str!("menus.rs")), 1299 ALLOWED_MENU_LITERALS 1300 .iter() 1301 .copied() 1302 .collect::<BTreeSet<_>>() 1303 ); 1304 } 1305 1306 #[test] 1307 fn desktop_window_source_uses_localized_copy_paths() { 1308 assert_eq!( 1309 extract_string_literals(include_str!("window.rs")), 1310 ALLOWED_WINDOW_LITERALS 1311 .iter() 1312 .copied() 1313 .collect::<BTreeSet<_>>() 1314 ); 1315 } 1316 1317 #[test] 1318 fn desktop_window_source_keeps_shell_reset_copy_keyed() { 1319 let source = include_str!("window.rs"); 1320 1321 for copy_key in REQUIRED_WINDOW_COPY_KEYS { 1322 assert!( 1323 source.contains(copy_key), 1324 "desktop window is missing localized copy key {copy_key}" 1325 ); 1326 } 1327 } 1328 1329 #[test] 1330 fn desktop_window_source_uses_settings_width_theme_token() { 1331 let source = include_str!("window.rs"); 1332 1333 assert!( 1334 !source.contains("Some(560.0)"), 1335 "settings panel width caps must use APP_UI_THEME.shells.settings_panel_content_max_width_px" 1336 ); 1337 assert!( 1338 source.contains("settings_panel_content_max_width_px"), 1339 "settings panel width token is not used by window.rs" 1340 ); 1341 } 1342 1343 #[test] 1344 fn desktop_launcher_source_keeps_shared_ui_boundary_enforced() { 1345 for (path, source) in launcher_source_files() { 1346 for (pattern, reason) in FORBIDDEN_LAUNCHER_UI_BYPASS_PATTERNS { 1347 assert!( 1348 !source.contains(pattern), 1349 "{} contains forbidden UI bypass pattern `{pattern}`: {reason}", 1350 path.display() 1351 ); 1352 } 1353 } 1354 } 1355 1356 #[test] 1357 fn desktop_window_source_does_not_reintroduce_removed_ui_helper_families() { 1358 let source = include_str!("window.rs"); 1359 1360 for helper_name in REMOVED_WINDOW_HELPER_FAMILIES { 1361 assert!( 1362 !source.contains(helper_name), 1363 "window.rs reintroduced removed launcher-local helper family `{helper_name}`" 1364 ); 1365 } 1366 } 1367 1368 #[test] 1369 fn app_sources_use_publish_lifecycle_action_identifiers() { 1370 for (path, source) in seller_lifecycle_action_owner_sources() { 1371 for pattern in FORBIDDEN_STALE_SELLER_LIFECYCLE_PATTERNS { 1372 assert!( 1373 !source.contains(pattern), 1374 "{} still contains stale seller lifecycle action pattern `{pattern}`", 1375 path.display() 1376 ); 1377 } 1378 } 1379 } 1380 1381 #[test] 1382 fn desktop_window_source_does_not_use_about_placeholder_copy() { 1383 let source = include_str!("window.rs"); 1384 1385 assert!( 1386 !source.contains("SettingsAboutPlaceholder"), 1387 "window.rs still references retired about placeholder copy" 1388 ); 1389 } 1390 1391 #[test] 1392 fn desktop_sources_do_not_hardcode_workflow_ui_copy() { 1393 for (path, source) in launcher_source_files() { 1394 let literals = extract_string_literals(&source); 1395 for literal in literals { 1396 for forbidden_literal in FORBIDDEN_HARDCODED_WORKFLOW_UI_LITERALS { 1397 assert_ne!( 1398 literal, 1399 *forbidden_literal, 1400 "{} hardcodes workflow UI copy `{forbidden_literal}`", 1401 path.display() 1402 ); 1403 } 1404 } 1405 } 1406 } 1407 1408 #[test] 1409 fn desktop_sources_do_not_expose_reserved_payment_action_copy() { 1410 for (path, source) in launcher_source_files() { 1411 for literal in extract_string_literals(&source) { 1412 let normalized_literal = literal.to_lowercase(); 1413 for pattern in FORBIDDEN_PAYMENT_DEFERRAL_COPY_PATTERNS { 1414 assert!( 1415 !normalized_literal.contains(pattern), 1416 "{} contains forbidden payment-deferral copy `{pattern}`", 1417 path.display() 1418 ); 1419 } 1420 for term in FORBIDDEN_PAYMENT_ACTION_COPY_TERMS { 1421 assert!( 1422 !contains_reserved_payment_action_term(&normalized_literal, term), 1423 "{} contains reserved payment action copy `{term}` in `{literal}`", 1424 path.display() 1425 ); 1426 } 1427 } 1428 } 1429 } 1430 1431 #[test] 1432 fn app_production_trade_event_kinds_use_shared_constants() { 1433 assert_production_source_omits_event_kind_literals( 1434 "crates/desktop/src/runtime.rs", 1435 include_str!("runtime.rs"), 1436 ); 1437 1438 let store_interop_path = Path::new(env!("CARGO_MANIFEST_DIR")) 1439 .parent() 1440 .and_then(|path| path.parent()) 1441 .expect("desktop crate should live under app crates directory") 1442 .join("crates/store/src/interop.rs"); 1443 let store_interop_source = 1444 fs::read_to_string(store_interop_path.as_path()).unwrap_or_else(|error| { 1445 panic!( 1446 "failed to read app store interop source {}: {error}", 1447 store_interop_path.display() 1448 ) 1449 }); 1450 1451 assert_production_source_omits_event_kind_literals( 1452 "crates/store/src/interop.rs", 1453 store_interop_source.as_str(), 1454 ); 1455 } 1456 1457 #[test] 1458 fn app_production_sdk_boundary_usage_is_allowlisted() { 1459 for (relative_path, source) in app_rust_source_files() { 1460 let production_source = production_source_without_tests(&source); 1461 let findings = 1462 unallowlisted_sdk_boundary_patterns(relative_path.as_str(), production_source); 1463 1464 assert!( 1465 findings.is_empty(), 1466 "{} contains unallowlisted SDK boundary pattern `{}`: {}", 1467 relative_path, 1468 findings.first().map_or("", |finding| finding.pattern), 1469 findings.first().map_or("", |finding| finding.reason) 1470 ); 1471 } 1472 } 1473 1474 #[test] 1475 fn strict_sdk_boundary_scanner_rejects_unallowlisted_new_production_paths() { 1476 let findings = unallowlisted_sdk_boundary_patterns( 1477 "crates/desktop/src/new_workflow.rs", 1478 "fn publish() { let _ = RadrootsSdkClient::from_config(config); }", 1479 ); 1480 1481 assert_eq!(findings.len(), 1); 1482 assert_eq!(findings[0].pattern, "RadrootsSdkClient"); 1483 let runtime_findings = unallowlisted_sdk_boundary_patterns( 1484 "crates/desktop/src/runtime.rs", 1485 "fn publish() { let _ = RadrootsSdkClient::from_config(config); }", 1486 ); 1487 assert_eq!(runtime_findings.len(), 1); 1488 assert_eq!(runtime_findings[0].pattern, "RadrootsSdkClient"); 1489 assert!( 1490 unallowlisted_sdk_boundary_patterns( 1491 "crates/desktop/src/accounts.rs", 1492 "fn import() { let _ = RawSecretKey; }", 1493 ) 1494 .is_empty() 1495 ); 1496 } 1497 1498 #[test] 1499 fn app_legacy_sdk_boundary_allowlist_entries_are_complete_and_current() { 1500 let app_root = app_root(); 1501 let mut entries = BTreeSet::new(); 1502 1503 for entry in LEGACY_SDK_BOUNDARY_ALLOWLIST { 1504 assert!( 1505 entries.insert((entry.path, entry.pattern)), 1506 "duplicate legacy SDK boundary allowlist entry {} `{}`", 1507 entry.path, 1508 entry.pattern 1509 ); 1510 assert!( 1511 !entry.owner.trim().is_empty(), 1512 "{} `{}` is missing an owner", 1513 entry.path, 1514 entry.pattern 1515 ); 1516 assert!( 1517 !entry.reason.trim().is_empty(), 1518 "{} `{}` is missing a reason", 1519 entry.path, 1520 entry.pattern 1521 ); 1522 assert!( 1523 !entry.removal_condition.trim().is_empty(), 1524 "{} `{}` is missing a removal condition", 1525 entry.path, 1526 entry.pattern 1527 ); 1528 1529 let source_path = app_root.join(entry.path); 1530 let source = read_source_path(source_path.as_path()); 1531 let production_source = production_source_without_tests(&source); 1532 assert!( 1533 production_source.contains(entry.pattern), 1534 "{} allowlists legacy SDK boundary pattern `{}` that is no longer present", 1535 entry.path, 1536 entry.pattern 1537 ); 1538 } 1539 } 1540 1541 fn extract_string_literals(source: &str) -> BTreeSet<&str> { 1542 let mut literals = BTreeSet::new(); 1543 let bytes = source.as_bytes(); 1544 let mut start = None; 1545 let mut escaped = false; 1546 1547 for (index, byte) in bytes.iter().copied().enumerate() { 1548 match (start, byte, escaped) { 1549 (None, b'"', _) => start = Some(index + 1), 1550 (Some(_), b'\\', false) => escaped = true, 1551 (Some(begin), b'"', false) => { 1552 literals.insert(&source[begin..index]); 1553 start = None; 1554 } 1555 (Some(_), _, true) => escaped = false, 1556 _ => {} 1557 } 1558 } 1559 1560 literals 1561 } 1562 1563 fn assert_production_source_omits_event_kind_literals(path: &str, source: &str) { 1564 let production_source = production_source_without_tests(source); 1565 for (literal, constant_name) in FORBIDDEN_PRODUCTION_EVENT_KIND_LITERALS { 1566 assert!( 1567 !contains_numeric_token(production_source, literal), 1568 "{path} uses raw event kind {literal}; use shared {constant_name} instead" 1569 ); 1570 } 1571 } 1572 1573 fn production_source_without_tests(source: &str) -> &str { 1574 source 1575 .split_once(TEST_MODULE_SENTINEL) 1576 .map_or(source, |(production_source, _)| production_source) 1577 } 1578 1579 fn unallowlisted_sdk_boundary_patterns( 1580 path: &str, 1581 production_source: &str, 1582 ) -> Vec<&'static SdkBoundaryForbiddenPattern> { 1583 STRICT_SDK_BOUNDARY_FORBIDDEN_PATTERNS 1584 .iter() 1585 .filter(|forbidden| production_source.contains(forbidden.pattern)) 1586 .filter(|forbidden| !legacy_sdk_boundary_allowlist_contains(path, forbidden.pattern)) 1587 .collect() 1588 } 1589 1590 fn legacy_sdk_boundary_allowlist_contains(path: &str, pattern: &str) -> bool { 1591 LEGACY_SDK_BOUNDARY_ALLOWLIST 1592 .iter() 1593 .any(|entry| entry.path == path && entry.pattern == pattern) 1594 } 1595 1596 fn read_source_path(path: &Path) -> String { 1597 fs::read_to_string(path) 1598 .unwrap_or_else(|error| panic!("failed to read source {}: {error}", path.display())) 1599 } 1600 1601 fn app_root() -> PathBuf { 1602 Path::new(env!("CARGO_MANIFEST_DIR")) 1603 .parent() 1604 .and_then(|path| path.parent()) 1605 .expect("desktop crate should live under app crates directory") 1606 .to_path_buf() 1607 } 1608 1609 fn app_rust_source_files() -> Vec<(String, String)> { 1610 let app_root = app_root(); 1611 let mut paths = Vec::new(); 1612 collect_rust_source_files(app_root.join("crates").as_path(), &mut paths); 1613 paths.sort(); 1614 paths 1615 .into_iter() 1616 .filter(|path| path.file_name().and_then(|name| name.to_str()) != Some("source_guards.rs")) 1617 .map(|path| { 1618 let relative_path = path 1619 .strip_prefix(app_root.as_path()) 1620 .unwrap_or_else(|error| { 1621 panic!( 1622 "failed to derive app-relative source path {}: {error}", 1623 path.display() 1624 ) 1625 }) 1626 .to_string_lossy() 1627 .replace('\\', "/"); 1628 let source = read_source_path(path.as_path()); 1629 (relative_path, source) 1630 }) 1631 .collect() 1632 } 1633 1634 fn contains_numeric_token(source: &str, literal: &str) -> bool { 1635 source.match_indices(literal).any(|(start, _)| { 1636 let end = start + literal.len(); 1637 let before_ok = start == 0 || !is_rust_identifier_byte(source.as_bytes()[start - 1]); 1638 let after_ok = end == source.len() || !source.as_bytes()[end].is_ascii_digit(); 1639 before_ok && after_ok 1640 }) 1641 } 1642 1643 fn is_rust_identifier_byte(byte: u8) -> bool { 1644 byte.is_ascii_alphanumeric() || byte == b'_' 1645 } 1646 1647 fn contains_reserved_payment_action_term(value: &str, term: &str) -> bool { 1648 if term.contains(' ') || term.contains('-') { 1649 return value.contains(term); 1650 } 1651 1652 value.match_indices(term).any(|(start, _)| { 1653 let end = start + term.len(); 1654 is_reserved_payment_term_boundary_before(value, start) 1655 && is_reserved_payment_term_boundary_after(value, end) 1656 }) 1657 } 1658 1659 fn is_reserved_payment_term_boundary_before(value: &str, index: usize) -> bool { 1660 if index == 0 { 1661 return true; 1662 } 1663 1664 is_reserved_payment_term_boundary_byte(value.as_bytes()[index - 1]) 1665 } 1666 1667 fn is_reserved_payment_term_boundary_after(value: &str, index: usize) -> bool { 1668 if index == value.len() { 1669 return true; 1670 } 1671 1672 is_reserved_payment_term_boundary_byte(value.as_bytes()[index]) 1673 } 1674 1675 fn is_reserved_payment_term_boundary_byte(byte: u8) -> bool { 1676 !byte.is_ascii_alphanumeric() && byte != b'_' && byte != b'-' 1677 } 1678 1679 fn launcher_source_files() -> Vec<(PathBuf, String)> { 1680 let mut paths = Vec::new(); 1681 collect_rust_source_files( 1682 Path::new(env!("CARGO_MANIFEST_DIR")).join("src").as_path(), 1683 &mut paths, 1684 ); 1685 paths.sort(); 1686 paths 1687 .into_iter() 1688 .filter(|path| path.file_name().and_then(|name| name.to_str()) != Some("source_guards.rs")) 1689 .map(|path| { 1690 let source = fs::read_to_string(&path).unwrap_or_else(|error| { 1691 panic!("failed to read launcher source {}: {error}", path.display()) 1692 }); 1693 (path, source) 1694 }) 1695 .collect() 1696 } 1697 1698 fn seller_lifecycle_action_owner_sources() -> Vec<(PathBuf, String)> { 1699 let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); 1700 let app_root = manifest_dir 1701 .parent() 1702 .and_then(|path| path.parent()) 1703 .expect("desktop crate should live under app crates directory"); 1704 [ 1705 manifest_dir.join("src/window.rs"), 1706 manifest_dir.join("src/runtime.rs"), 1707 app_root.join("crates/view/src/lib.rs"), 1708 app_root.join("crates/store/src/repo/orders.rs"), 1709 app_root.join("crates/i18n/src/keys.rs"), 1710 app_root.join("crates/i18n/src/lib.rs"), 1711 app_root.join("i18n/locales/en/messages.json"), 1712 ] 1713 .into_iter() 1714 .map(|path| { 1715 let source = fs::read_to_string(&path).unwrap_or_else(|error| { 1716 panic!( 1717 "failed to read seller lifecycle source {}: {error}", 1718 path.display() 1719 ) 1720 }); 1721 (path, source) 1722 }) 1723 .collect() 1724 } 1725 1726 fn collect_rust_source_files(root: &Path, paths: &mut Vec<PathBuf>) { 1727 let entries = fs::read_dir(root).unwrap_or_else(|error| { 1728 panic!( 1729 "failed to read launcher source directory {}: {error}", 1730 root.display() 1731 ) 1732 }); 1733 1734 for entry in entries { 1735 let entry = entry.unwrap_or_else(|error| { 1736 panic!( 1737 "failed to inspect launcher source directory {}: {error}", 1738 root.display() 1739 ) 1740 }); 1741 let path = entry.path(); 1742 1743 if path.is_dir() { 1744 collect_rust_source_files(path.as_path(), paths); 1745 continue; 1746 } 1747 1748 if path.extension().and_then(|extension| extension.to_str()) == Some("rs") { 1749 paths.push(path); 1750 } 1751 } 1752 }