Each of these classes wrapped an existing free function with a one-line delegating method and was never instantiated anywhere (verified: no references outside their own translation unit, not even within their own .cpp beyond the definition) — the redundant "wrapper layer" pattern CLAUDE.md warns against: - LiteWalletLifecycleUiExecutionAdapter -> executeLiteWalletLifecycleUiRequest - LiteWalletServerSelectionUiExecutionAdapter -> executeLiteWalletServerSelectionUi - LiteWalletServerLifecycleReadinessPlanner -> evaluateLiteWalletServerLifecycleReadiness - LiteBackendActivationReadinessAdapter -> evaluateLiteBackendActivationReadiness The live free functions (the actual entry points used by the UI/runtime) are unchanged. Both targets build, test suite passes, source-hygiene clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
386 lines
18 KiB
C++
386 lines
18 KiB
C++
#include "wallet/lite_wallet_server_lifecycle_readiness.h"
|
|
|
|
#include <cctype>
|
|
#include <utility>
|
|
|
|
namespace dragonx::wallet {
|
|
namespace {
|
|
|
|
std::string trimCopy(const std::string& value)
|
|
{
|
|
auto begin = value.begin();
|
|
while (begin != value.end() && std::isspace(static_cast<unsigned char>(*begin))) ++begin;
|
|
|
|
auto end = value.end();
|
|
while (end != begin && std::isspace(static_cast<unsigned char>(*(end - 1)))) --end;
|
|
|
|
return std::string(begin, end);
|
|
}
|
|
|
|
void addIssue(LiteWalletServerLifecycleReadinessResult& result,
|
|
LiteWalletServerLifecycleReadinessIssue issue,
|
|
std::string message)
|
|
{
|
|
result.issues.push_back(LiteWalletServerLifecycleReadinessIssueInfo{issue, std::move(message)});
|
|
}
|
|
|
|
LiteWalletServerLifecycleReadinessResult stoppedResult(
|
|
LiteWalletServerLifecycleReadinessResult result,
|
|
LiteWalletServerLifecycleReadinessStatus status,
|
|
LiteWalletServerLifecycleReadinessIssue issue,
|
|
std::string message)
|
|
{
|
|
result.status = status;
|
|
addIssue(result, issue, std::move(message));
|
|
result.error = result.issues.back().message;
|
|
result.lifecycleStatus = WalletBackendStatus{WalletBackendState::Unavailable, result.error, {}, {}, 0.0};
|
|
return result;
|
|
}
|
|
|
|
LiteRedactedPrivateData redactedPrivateField(LitePrivateDataKind kind, const std::string& value)
|
|
{
|
|
return LiteRedactedPrivateData{kind, !trimCopy(value).empty(), redactLitePrivateDataValue(value)};
|
|
}
|
|
|
|
bool privateDataIsRedacted(const std::vector<LiteRedactedPrivateData>& privateData)
|
|
{
|
|
for (const auto& item : privateData) {
|
|
if (!item.present && item.redactedValue != "<empty>") return false;
|
|
if (item.present && item.redactedValue != "<redacted>") return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
LiteWalletSelectedServerDisplayReport displayReportForSelection(
|
|
const LiteServerSelectionResult& selection)
|
|
{
|
|
LiteWalletSelectedServerDisplayReport report;
|
|
if (!selection.ok) {
|
|
report.status = LiteWalletSelectedServerDisplayStatus::Missing;
|
|
report.message = selection.error.empty() ? "no usable lite server is selected" : selection.error;
|
|
return report;
|
|
}
|
|
|
|
report.ok = true;
|
|
report.status = selection.customServer
|
|
? LiteWalletSelectedServerDisplayStatus::CustomServer
|
|
: LiteWalletSelectedServerDisplayStatus::SelectedServer;
|
|
report.label = selection.server.label;
|
|
report.url = selection.server.url;
|
|
report.serverIndex = selection.serverIndex;
|
|
report.customServer = selection.customServer;
|
|
report.message = selection.customServer
|
|
? "custom lite server selected for display"
|
|
: "configured lite server selected for display";
|
|
return report;
|
|
}
|
|
|
|
LiteWalletLifecyclePlan lifecyclePlanForRequest(
|
|
LiteWalletLifecycleOperation operation,
|
|
const LiteServerSelectionResult& selection)
|
|
{
|
|
LiteWalletLifecyclePlan plan;
|
|
plan.operation = operation;
|
|
plan.bridgeExecutionAllowed = false;
|
|
if (!selection.ok) {
|
|
plan.error = selection.error.empty() ? "no usable lite server is selected" : selection.error;
|
|
return plan;
|
|
}
|
|
|
|
plan.ok = true;
|
|
plan.server = selection.server;
|
|
plan.serverIndex = selection.serverIndex;
|
|
plan.customServer = selection.customServer;
|
|
return plan;
|
|
}
|
|
|
|
LiteWalletLifecycleUiRequestPlan requestPlanForInput(
|
|
const LiteWalletServerLifecycleReadinessInput& input,
|
|
const LiteServerSelectionResult& selection)
|
|
{
|
|
LiteWalletLifecycleUiRequestPlan plan;
|
|
plan.operation = input.operation;
|
|
plan.lifecyclePlan = lifecyclePlanForRequest(input.operation, selection);
|
|
if (!plan.lifecyclePlan.ok) {
|
|
plan.error = plan.lifecyclePlan.error;
|
|
return plan;
|
|
}
|
|
|
|
switch (input.operation) {
|
|
case LiteWalletLifecycleOperation::CreateNew:
|
|
plan.privateData.push_back(redactedPrivateField(
|
|
LitePrivateDataKind::Passphrase,
|
|
input.createRequest.passphrase));
|
|
plan.requestSummary = "operation=create;server=<selected>;passphrase=" +
|
|
std::string(input.createRequest.passphrase.empty() ? "<empty>" : "<redacted>");
|
|
break;
|
|
case LiteWalletLifecycleOperation::OpenExisting:
|
|
if (trimCopy(input.openRequest.walletPath).empty()) {
|
|
plan.error = "lite wallet open requires a wallet path selected by the UI";
|
|
return plan;
|
|
}
|
|
plan.privateData.push_back(redactedPrivateField(
|
|
LitePrivateDataKind::WalletPath,
|
|
input.openRequest.walletPath));
|
|
plan.privateData.push_back(redactedPrivateField(
|
|
LitePrivateDataKind::Passphrase,
|
|
input.openRequest.passphrase));
|
|
plan.requestSummary = "operation=open;server=<selected>;wallet_path=<redacted>;passphrase=" +
|
|
std::string(input.openRequest.passphrase.empty() ? "<empty>" : "<redacted>");
|
|
break;
|
|
case LiteWalletLifecycleOperation::RestoreFromSeed:
|
|
if (trimCopy(input.restoreRequest.seedPhrase).empty()) {
|
|
plan.error = "lite wallet restore requires redacted seed material metadata";
|
|
return plan;
|
|
}
|
|
plan.privateData.push_back(redactedPrivateField(
|
|
LitePrivateDataKind::SeedPhrase,
|
|
input.restoreRequest.seedPhrase));
|
|
plan.privateData.push_back(redactedPrivateField(
|
|
LitePrivateDataKind::WalletPath,
|
|
input.restoreRequest.walletPath));
|
|
plan.privateData.push_back(redactedPrivateField(
|
|
LitePrivateDataKind::Passphrase,
|
|
input.restoreRequest.passphrase));
|
|
plan.requestSummary = "operation=restore;server=<selected>;seed=<redacted>;wallet_path=" +
|
|
std::string(input.restoreRequest.walletPath.empty() ? "<empty>" : "<redacted>") +
|
|
";passphrase=" +
|
|
std::string(input.restoreRequest.passphrase.empty() ? "<empty>" : "<redacted>");
|
|
break;
|
|
}
|
|
|
|
plan.privateInputsRedacted = privateDataIsRedacted(plan.privateData);
|
|
plan.lifecyclePlan.privateData = plan.privateData;
|
|
plan.ok = plan.privateInputsRedacted;
|
|
if (!plan.ok) plan.error = "lite wallet lifecycle private inputs are not redacted";
|
|
return plan;
|
|
}
|
|
|
|
LiteWalletServerSelectionPersistencePlan persistencePlanForInput(
|
|
const LiteWalletServerLifecycleReadinessInput& input,
|
|
const LiteServerSelectionResult& selection)
|
|
{
|
|
LiteWalletServerSelectionPersistencePlan plan;
|
|
plan.settingsLoaded = input.persistence.settingsLoaded;
|
|
plan.persistedSelectionIntentAccepted = input.persistence.havePersistedSelectionIntent;
|
|
plan.wouldPersistSelectedServer = input.persistence.persistSelectedServer;
|
|
plan.persistenceOwnerAccepted = input.persistence.persistenceOwnerReady;
|
|
plan.selectionMode = input.settings.selectionMode;
|
|
if (selection.ok) {
|
|
plan.selectedServerUrl = selection.server.url;
|
|
plan.selectedServerIndex = selection.serverIndex;
|
|
plan.selectedServerCustom = selection.customServer;
|
|
}
|
|
plan.settingsWritten = false;
|
|
plan.ok = true;
|
|
return plan;
|
|
}
|
|
|
|
WalletBackendStatus readyLifecycleStatus(const LiteWalletSelectedServerDisplayReport& display)
|
|
{
|
|
const std::string serverLabel = display.label.empty() ? display.url : display.label;
|
|
return WalletBackendStatus{
|
|
WalletBackendState::Disconnected,
|
|
"lite lifecycle UI readiness accepted for " + serverLabel + "; wallet lifecycle execution is still disabled",
|
|
{},
|
|
{},
|
|
0.0
|
|
};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const char* liteWalletServerLifecycleReadinessStatusName(
|
|
LiteWalletServerLifecycleReadinessStatus status)
|
|
{
|
|
switch (status) {
|
|
case LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle: return "ReadyForFutureLifecycle";
|
|
case LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild: return "WaitingForLiteBuild";
|
|
case LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability: return "WaitingForBackendCapability";
|
|
case LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection: return "WaitingForServerSelection";
|
|
case LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent: return "WaitingForPersistenceIntent";
|
|
case LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus: return "WaitingForDisplayStatus";
|
|
case LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi: return "WaitingForLifecycleUi";
|
|
case LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction: return "WaitingForPrivateDataRedaction";
|
|
case LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed: return "WaitingForSyncPlannerFeed";
|
|
case LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled: return "RuntimeExecutionDisabled";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
const char* liteWalletServerLifecycleReadinessIssueName(
|
|
LiteWalletServerLifecycleReadinessIssue issue)
|
|
{
|
|
switch (issue) {
|
|
case LiteWalletServerLifecycleReadinessIssue::FullNodeBuild: return "FullNodeBuild";
|
|
case LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing: return "LiteBackendCapabilityMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded: return "PersistedSettingsNotLoaded";
|
|
case LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing: return "PersistedServerSelectionIntentMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing: return "ServerSelectionMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing: return "ServerPersistenceOwnerMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing: return "SelectedServerDisplayMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing: return "LifecycleUiOwnerMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed: return "LifecycleOperationUnconfirmed";
|
|
case LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing: return "OpenWalletPathMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing: return "RestoreSeedMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing: return "PrivateDataRedactionMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing: return "SyncPlannerFeedMissing";
|
|
case LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested: return "RealLifecycleExecutionRequested";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
const char* liteWalletSelectedServerDisplayStatusName(
|
|
LiteWalletSelectedServerDisplayStatus status)
|
|
{
|
|
switch (status) {
|
|
case LiteWalletSelectedServerDisplayStatus::Hidden: return "Hidden";
|
|
case LiteWalletSelectedServerDisplayStatus::SelectedServer: return "SelectedServer";
|
|
case LiteWalletSelectedServerDisplayStatus::CustomServer: return "CustomServer";
|
|
case LiteWalletSelectedServerDisplayStatus::Missing: return "Missing";
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
LiteWalletServerLifecycleReadinessResult evaluateLiteWalletServerLifecycleReadiness(
|
|
const LiteWalletServerLifecycleReadinessInput& input,
|
|
LiteWalletServerLifecycleReadinessOptions options)
|
|
{
|
|
LiteWalletServerLifecycleReadinessResult result;
|
|
result.capabilities = input.capabilities;
|
|
result.persistencePlan = persistencePlanForInput(input, {});
|
|
|
|
if (options.requireLiteBuild && !isLiteBuild(input.capabilities)) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForLiteBuild,
|
|
LiteWalletServerLifecycleReadinessIssue::FullNodeBuild,
|
|
"lite server lifecycle readiness requires a lite build");
|
|
}
|
|
result.liteBuildAccepted = true;
|
|
|
|
if (options.requireLiteBackendCapability && !supportsLiteBackend(input.capabilities)) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForBackendCapability,
|
|
LiteWalletServerLifecycleReadinessIssue::LiteBackendCapabilityMissing,
|
|
"lite backend capability is required before lifecycle UI readiness can feed sync planners");
|
|
}
|
|
result.backendCapabilityAccepted = true;
|
|
|
|
if (options.requirePersistedSettingsLoaded && !input.persistence.settingsLoaded) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent,
|
|
LiteWalletServerLifecycleReadinessIssue::PersistedSettingsNotLoaded,
|
|
"lite server settings must be loaded before lifecycle UI readiness is evaluated");
|
|
}
|
|
|
|
if (options.requirePersistedSelectionIntent && !input.persistence.havePersistedSelectionIntent) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent,
|
|
LiteWalletServerLifecycleReadinessIssue::PersistedServerSelectionIntentMissing,
|
|
"lite server selection persistence intent is missing");
|
|
}
|
|
|
|
result.selectedServer = selectLiteServer(input.settings);
|
|
result.persistencePlan = persistencePlanForInput(input, result.selectedServer);
|
|
if (!result.selectedServer.ok) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForServerSelection,
|
|
LiteWalletServerLifecycleReadinessIssue::ServerSelectionMissing,
|
|
result.selectedServer.error.empty()
|
|
? "no usable lite server is selected"
|
|
: result.selectedServer.error);
|
|
}
|
|
result.serverSelectionAccepted = true;
|
|
|
|
if (input.persistence.persistSelectedServer && !input.persistence.persistenceOwnerReady) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForPersistenceIntent,
|
|
LiteWalletServerLifecycleReadinessIssue::ServerPersistenceOwnerMissing,
|
|
"lite server selection persistence owner is not ready");
|
|
}
|
|
result.persistenceIntentAccepted = true;
|
|
result.persistencePlan.ok = true;
|
|
|
|
result.selectedServerDisplay = displayReportForSelection(result.selectedServer);
|
|
if (options.requireSelectedServerDisplay && !input.ui.selectedServerDisplayReady) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForDisplayStatus,
|
|
LiteWalletServerLifecycleReadinessIssue::SelectedServerDisplayMissing,
|
|
"selected lite server display status is not ready");
|
|
}
|
|
result.selectedServerDisplayAccepted = true;
|
|
|
|
if (options.requireLifecycleUiOwner && !input.ui.lifecycleUiOwnerReady) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi,
|
|
LiteWalletServerLifecycleReadinessIssue::LifecycleUiOwnerMissing,
|
|
"lite wallet lifecycle UI owner is not ready");
|
|
}
|
|
result.lifecycleUiOwnerAccepted = true;
|
|
|
|
if (input.ui.realLifecycleExecutionRequested) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::RuntimeExecutionDisabled,
|
|
LiteWalletServerLifecycleReadinessIssue::RealLifecycleExecutionRequested,
|
|
"real lite wallet lifecycle execution is disabled in this scaffold");
|
|
}
|
|
|
|
if (options.requireOperationConfirmation && !input.ui.operationConfirmed) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi,
|
|
LiteWalletServerLifecycleReadinessIssue::LifecycleOperationUnconfirmed,
|
|
"lite wallet lifecycle operation requires explicit UI confirmation");
|
|
}
|
|
|
|
result.requestPlan = requestPlanForInput(input, result.selectedServer);
|
|
if (!result.requestPlan.ok) {
|
|
const auto issue = input.operation == LiteWalletLifecycleOperation::OpenExisting
|
|
? LiteWalletServerLifecycleReadinessIssue::OpenWalletPathMissing
|
|
: LiteWalletServerLifecycleReadinessIssue::RestoreSeedMissing;
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForLifecycleUi,
|
|
issue,
|
|
result.requestPlan.error.empty()
|
|
? "lite wallet lifecycle UI request is incomplete"
|
|
: result.requestPlan.error);
|
|
}
|
|
result.requestAccepted = true;
|
|
|
|
if (options.requirePrivateDataRedaction && !input.ui.privateDataRedactionReady) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForPrivateDataRedaction,
|
|
LiteWalletServerLifecycleReadinessIssue::PrivateDataRedactionMissing,
|
|
"lite wallet lifecycle private-data redaction owner is not ready");
|
|
}
|
|
result.privateDataRedactionAccepted = true;
|
|
|
|
if (options.requireSyncPlannerFeed && !input.ui.syncPlannerFeedReady) {
|
|
return stoppedResult(
|
|
std::move(result),
|
|
LiteWalletServerLifecycleReadinessStatus::WaitingForSyncPlannerFeed,
|
|
LiteWalletServerLifecycleReadinessIssue::SyncPlannerFeedMissing,
|
|
"lite lifecycle readiness feed for sync planners is not ready");
|
|
}
|
|
result.syncPlannerFeedAccepted = true;
|
|
|
|
result.ok = true;
|
|
result.status = LiteWalletServerLifecycleReadinessStatus::ReadyForFutureLifecycle;
|
|
result.futureLifecycleCouldBeEnabled = true;
|
|
result.lifecycleReportCouldFeedSyncPlanners = true;
|
|
result.lifecycleStatus = readyLifecycleStatus(result.selectedServerDisplay);
|
|
return result;
|
|
}
|
|
|
|
} // namespace dragonx::wallet
|