Files
ObsidianDragon/tools/hushchat_fixture_check.cpp
DanS 7891c689fb refactor(audit): batch 2 — remove dead scaffolding from the shipping binary
Two dead-code removals surfaced by the audit; no runtime behavior change.

1. Delete the lite-lifecycle "readiness" scaffold (~1,586 lines). The pure
   dry-run evaluateLiteWalletServerLifecycleReadiness (whose own status text
   says "wallet lifecycle execution is still disabled in this scaffold") had
   zero callers; executeLiteWalletServerSelectionUi had zero callers; and
   executeLiteWalletLifecycleUiRequest's only caller was a fallback in
   settings_page reached only when app->liteWallet() is null. Deleted
   lite_wallet_server_lifecycle_readiness.{h,cpp} and the readiness /
   UI-execution machinery + enums + translation tables from both adapters,
   keeping the genuinely-live helpers (liteConnectionSettingsFromAppSettings,
   applyLiteConnectionSettingsToAppSettings, redactLiteServerSelectionValue,
   and the LiteWalletLifecycleUiExecutionInput DTO the live create/open/
   restore path still uses). The null-backend fallback now sets a plain
   "Lite wallet backend unavailable" status. This is exactly the
   readiness/scaffold pattern CLAUDE.md forbids regrowing.

2. Split the HushChat fixture/capture-manifest/seed-projection tooling
   (~2,050 lines, incl. the libsodium seed-projection) out of
   chat_protocol.cpp (1854 -> 284) and chat_protocol.h (586 -> 105) into a
   new chat_fixture_tooling.{h,cpp} compiled ONLY into the HushChatFixtureCheck
   dev tool — never into the app, lite, or test binaries (verified via nm).
   The app keeps only the runtime slice (memo parsing + tx metadata
   extraction) that network_refresh_service actually calls. Also moved the
   decrypt-preflight/hex helpers, which likewise had no runtime caller.

Note: the HushChat split is on gated-off experimental code and should be
coordinated with the pending dormant-HushChat-content handling (commit
af06b8b) before the lite PR — done here as a pure mechanical split, no redesign.

Full-node + Lite + HushChatFixtureCheck build clean; ctest 1/1; hygiene clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 16:20:53 -05:00

227 lines
9.7 KiB
C++

#include "chat/chat_fixture_tooling.h"
#include <algorithm>
#include <filesystem>
#include <iostream>
#include <string>
#include <vector>
namespace fs = std::filesystem;
namespace {
using dragonx::chat::HushChatCompatibilityFixtureImportCandidate;
using dragonx::chat::HushChatCompatibilityFixtureKind;
std::string baseName(const std::string& path)
{
return fs::path(path).filename().string();
}
std::vector<std::string> jsonFilesInDirectory(const fs::path& directory)
{
std::vector<std::string> paths;
if (!fs::exists(directory) || !fs::is_directory(directory)) return paths;
for (const auto& entry : fs::directory_iterator(directory)) {
if (!entry.is_regular_file()) continue;
if (entry.path().extension() != ".json") continue;
paths.push_back(entry.path().string());
}
std::sort(paths.begin(), paths.end());
return paths;
}
HushChatCompatibilityFixtureKind expectedKindFromPath(const std::string& path)
{
const std::string name = baseName(path);
if (name.find("outgoing") != std::string::npos) return HushChatCompatibilityFixtureKind::OutgoingMemo;
if (name.find("seed") != std::string::npos) return HushChatCompatibilityFixtureKind::SeedPublicKeyProjection;
if (name.find("corrupted") != std::string::npos) return HushChatCompatibilityFixtureKind::CorruptedAuthFailure;
if (name.find("cont") != std::string::npos) return HushChatCompatibilityFixtureKind::ContactExclusion;
return HushChatCompatibilityFixtureKind::IncomingMemo;
}
void printUsage(const char* program)
{
std::cerr << "Usage: " << program
<< " [--allow-pending] [--replacement-dry-run] [--validate-capture-manifest]"
<< " <fixture-file-or-directory>...\n";
}
const char* boolText(bool value)
{
return value ? "1" : "0";
}
void printReplacementDryRun(const dragonx::chat::HushChatCompatibilityFixtureReplacementDryRunResult& dryRun)
{
std::cout << "HushChat fixture replacement dry run: "
<< (dryRun.ok ? "Ready" : "Refused") << "\n";
std::cout << "error=" << dryRun.error_name
<< " dry_run_only=" << boolText(dryRun.dry_run_only)
<< " redacted=" << boolText(dryRun.redacted_report)
<< " would_replace=" << boolText(dryRun.would_replace)
<< " replacement_refused=" << boolText(dryRun.replacement_refused) << "\n";
std::cout << "required=" << dryRun.required_count
<< " supplied=" << dryRun.supplied_count
<< " missing=" << dryRun.missing_count
<< " verified=" << dryRun.verified_count
<< " seed_projected=" << dryRun.seed_projection_verified_count
<< " future_auth_required=" << dryRun.future_auth_failure_required_count
<< " auth_structural_ready=" << dryRun.auth_failure_structural_ready_count
<< " cont_excluded=" << dryRun.excluded_count
<< " pending=" << dryRun.pending_count
<< " rejected=" << dryRun.rejected_count << "\n";
for (const auto& item : dryRun.report_items) {
std::cout << dragonx::chat::hushChatCompatibilityFixtureKindName(item.expected_kind)
<< " decision=" << (item.replacement_eligible ? "ready" : "refused")
<< " error=" << item.error_name
<< " supplied=" << boolText(item.supplied)
<< " pending=" << boolText(item.pending)
<< " seed_projected=" << boolText(item.seed_projection_verified)
<< " future_auth_required=" << boolText(item.future_auth_failure_required)
<< " auth_structural_ready=" << boolText(item.structurally_ready_for_future_auth_check)
<< " cont_excluded=" << boolText(item.cont_excluded)
<< " decrypted=" << boolText(item.decrypted)
<< " authenticated=" << boolText(item.authenticated);
if (item.supplied) std::cout << " file=" << baseName(item.path);
std::cout << "\n";
}
}
std::string captureManifestPathFromInput(const std::string& input)
{
fs::path path(input);
if (fs::exists(path) && fs::is_directory(path)) return (path / "capture-manifest.json").string();
return input;
}
void printCaptureManifestValidation(const dragonx::chat::HushChatCaptureManifestValidationResult& validation)
{
std::cout << "HushChat capture manifest: " << (validation.ok ? "Ready" : "Refused") << "\n";
std::cout << "error=" << validation.error_name
<< " redacted=" << boolText(validation.redacted_report)
<< " provenance_only=" << boolText(validation.validates_provenance_only)
<< " no_sensitive_material=" << boolText(validation.no_sensitive_material_declared)
<< " dry_run_instruction=" << boolText(validation.has_dry_run_command);
if (!validation.manifest_path.empty()) std::cout << " manifest=" << baseName(validation.manifest_path);
if (!validation.fixture_directory.empty()) std::cout << " fixture_dir=" << baseName(validation.fixture_directory);
std::cout << "\n";
std::cout << "required=" << validation.required_count
<< " declared=" << validation.declared_count
<< " missing=" << validation.missing_count
<< " duplicate=" << validation.duplicate_count
<< " handling_flags=" << validation.handling_flag_count
<< " prohibited=" << validation.prohibited_field_count << "\n";
for (const auto& category : validation.categories) {
std::cout << dragonx::chat::hushChatCompatibilityFixtureKindName(category.kind)
<< " declared=" << boolText(category.declared);
if (!category.staged_filename.empty()) std::cout << " file=" << baseName(category.staged_filename);
std::cout << "\n";
}
}
} // namespace
int main(int argc, char** argv)
{
bool allowPending = false;
bool replacementDryRun = false;
bool validateCaptureManifest = false;
std::vector<std::string> inputPaths;
for (int index = 1; index < argc; ++index) {
const std::string arg = argv[index];
if (arg == "--allow-pending") {
allowPending = true;
} else if (arg == "--replacement-dry-run") {
replacementDryRun = true;
} else if (arg == "--validate-capture-manifest") {
validateCaptureManifest = true;
} else if (arg == "--help" || arg == "-h") {
printUsage(argv[0]);
return 0;
} else {
inputPaths.push_back(arg);
}
}
if (inputPaths.empty()) {
printUsage(argv[0]);
return 2;
}
if (validateCaptureManifest && (allowPending || replacementDryRun)) {
std::cerr << "Capture manifest validation is separate from fixture and replacement checks.\n";
return 2;
}
if (replacementDryRun && allowPending) {
std::cerr << "Replacement dry run is strict; remove --allow-pending.\n";
return 2;
}
if (validateCaptureManifest) {
bool allValid = true;
for (const auto& input : inputPaths) {
const auto validation = dragonx::chat::loadHushChatCaptureManifestFile(
captureManifestPathFromInput(input),
true);
printCaptureManifestValidation(validation);
if (!validation.ok) allValid = false;
}
return allValid ? 0 : 1;
}
std::vector<std::string> paths;
for (const auto& input : inputPaths) {
fs::path path(input);
if (fs::exists(path) && fs::is_directory(path)) {
auto directoryFiles = jsonFilesInDirectory(path);
paths.insert(paths.end(), directoryFiles.begin(), directoryFiles.end());
} else {
paths.push_back(input);
}
}
std::vector<HushChatCompatibilityFixtureImportCandidate> candidates;
candidates.reserve(paths.size());
for (const auto& path : paths) {
candidates.push_back(HushChatCompatibilityFixtureImportCandidate{expectedKindFromPath(path), path});
}
if (replacementDryRun) {
const auto dryRun = dragonx::chat::inspectHushChatCompatibilityFixtureReplacementDryRun(candidates, true);
printReplacementDryRun(dryRun);
return dryRun.ok ? 0 : 1;
}
const auto checklist = dragonx::chat::inspectHushChatCompatibilityFixtureImportChecklist(candidates, true);
std::cout << "HushChat fixture import checklist: " << checklist.error_name << "\n";
std::cout << "required=" << checklist.required_count
<< " supplied=" << checklist.supplied_count
<< " missing=" << checklist.missing_count
<< " verified=" << checklist.verified_count
<< " seed_projected=" << checklist.seed_projection_verified_count
<< " future_auth_required=" << checklist.future_auth_failure_required_count
<< " auth_structural_ready=" << checklist.auth_failure_structural_ready_count
<< " cont_excluded=" << checklist.excluded_count
<< " pending=" << checklist.pending_count
<< " rejected=" << checklist.rejected_count << "\n";
for (const auto& item : checklist.items) {
std::cout << dragonx::chat::hushChatCompatibilityFixtureKindName(item.expected_kind)
<< " " << item.error_name;
if (item.future_auth_failure_required) std::cout << " FutureAuthFailureRequired";
if (item.structurally_ready_for_future_auth_check) std::cout << " StructurallyReadyForFutureAuthCheck";
if (item.supplied) std::cout << " " << baseName(item.path);
std::cout << "\n";
}
if (checklist.replacement_ready) return 0;
if (allowPending && checklist.supplied_count == checklist.required_count &&
checklist.missing_count == 0 && checklist.pending_count > 0 && checklist.rejected_count == 0) return 0;
return 1;
}