Files
ObsidianDragon/tools/hushchat_fixture_check.cpp
DanS 863d015628 feat(lite): lite wallet foundation (inherited working-tree state)
Preserve the previously-uncommitted lite wallet implementation and related dev WIP
under version control:
- src/wallet/ lite services: client bridge, bridge runtime, connection, lifecycle,
  sync, gateway, result parsers, state mapper, artifact contract/resolver, refresh
  services, UI adapters, wallet_backend/capabilities. (Includes two small M1 fixes:
  lifecycle walletReady now parses the response; default chain name -> "main".)
- src/chat/ chat protocol; tests/fixtures/ (lite + hushchat); tools/hushchat_fixture_check.cpp;
  scripts/build-lite-backend-artifact.sh.
- Pre-existing modified app_network/security/wizard, network_refresh_service, sidebar,
  mining_tab, bootstrap dialog, and version headers captured as-is.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-04 21:15:28 -05:00

227 lines
9.7 KiB
C++

#include "chat/chat_protocol.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;
}