From 305b1bfcd1876d3c8a4dfc005f303b129de87a5f Mon Sep 17 00:00:00 2001 From: Autismo Date: Thu, 10 Sep 2026 10:19:40 +0200 Subject: [PATCH] Mint a candidate edit receipt after a passing bounded /edit check. A successful apply that is not rolled back now calls an optional receipt sink. The kernel writes a candidate Golden Record (sector local-edit, source_type edit_receipt, skill_promote_eligible=false). Failures and missing verifiers do not mint. local_edit_test covers pass, rollback, and missing-check paths. --- godbrain_core/cpp_kernel/local_edit.cpp | 54 ++++++++++++++++++++ godbrain_core/cpp_kernel/local_edit.h | 6 +++ godbrain_core/cpp_kernel/local_edit_test.cpp | 39 ++++++++++++-- godbrain_core/cpp_kernel/main.cpp | 16 ++++++ 4 files changed, 110 insertions(+), 5 deletions(-) diff --git a/godbrain_core/cpp_kernel/local_edit.cpp b/godbrain_core/cpp_kernel/local_edit.cpp index 6fae36d..e0ece15 100644 --- a/godbrain_core/cpp_kernel/local_edit.cpp +++ b/godbrain_core/cpp_kernel/local_edit.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,8 @@ namespace local_edit { static std::string g_verify_script_override; +static ReceiptSink g_receipt_sink; +static std::mutex g_receipt_mu; namespace { @@ -505,6 +508,8 @@ void save_result( << ",\"check_ran\":" << (check.ran ? "true" : "false") << ",\"check_ok\":" << (check.ok ? "true" : "false") << ",\"skill_promote_eligible\":false" + << ",\"receipt_saved\":" << (extra.receipt_saved ? "true" : "false") + << ",\"receipt_id\":\"" << json_escape(extra.receipt_id) << "\"" << ",\"before_hash\":\"" << json_escape(extra.before_hash) << "\"" << ",\"after_hash\":\"" << json_escape(extra.after_hash) << "\"" << ",\"preview_path\":\"" << json_escape(extra.preview_path) << "\"" @@ -805,6 +810,34 @@ void set_verify_script_for_test(const std::string& path) { g_verify_script_override = path; } +void set_receipt_sink(ReceiptSink sink) { + std::lock_guard lock(g_receipt_mu); + g_receipt_sink = std::move(sink); +} + +bool receipt_eligible(const Result& result) { + return result.applied && result.check_ran && result.check_ok && + !result.rolled_back; +} + +std::string format_edit_receipt(const Result& result) { + std::ostringstream out; + out << "/edit applied and bounded check passed (candidate until /verify).\n" + << "claim_class=playbook\n" + << "sector=local-edit\n" + << "source_type=edit_receipt\n" + << "skill_promote_eligible=false\n" + << "verification_profile=local-edit-apply-v1\n" + << "check_profile=" << result.check_profile << "\n" + << "path=" << result.preview_path << "\n" + << "before_hash=" << result.before_hash << "\n" + << "after_hash=" << result.after_hash << "\n" + << "check_ran=true\n" + << "check_ok=true\n" + << "rolled_back=false\n"; + return out.str(); +} + bool apply_still_open(const std::string& text) { const size_t apply_sp = text.rfind("*** APPLY"); const size_t apply_t = text.rfind("***APPLY"); @@ -947,6 +980,27 @@ Result maybe_apply( } result.report += "\n"; } + if (receipt_eligible(result)) { + ReceiptSink sink; + { + std::lock_guard receipt_lock(g_receipt_mu); + sink = g_receipt_sink; + } + if (sink) { + try { + const std::string id = sink(format_edit_receipt(result)); + if (!id.empty()) { + result.receipt_saved = true; + result.receipt_id = id; + result.report += "receipt candidate " + id.substr(0, 12) + "\n"; + } else { + result.report += "receipt fail\n"; + } + } catch (...) { + result.report += "receipt fail\n"; + } + } + } save_result(result.applied, result.rolled_back, result.report, check, result); return result; } diff --git a/godbrain_core/cpp_kernel/local_edit.h b/godbrain_core/cpp_kernel/local_edit.h index d3824aa..d3a315d 100644 --- a/godbrain_core/cpp_kernel/local_edit.h +++ b/godbrain_core/cpp_kernel/local_edit.h @@ -20,6 +20,8 @@ struct Result { std::string preview_path; std::string preview_old; std::string preview_new; + bool receipt_saved = false; + std::string receipt_id; }; struct Preview { @@ -36,6 +38,10 @@ std::string edit_user_with_excerpt(const std::string& user_msg); Preview preview_apply_blocks(const std::string& text); std::string check_profile_for(const std::string& rel); void set_verify_script_for_test(const std::string& path); +bool receipt_eligible(const Result& result); +std::string format_edit_receipt(const Result& result); +using ReceiptSink = std::function; +void set_receipt_sink(ReceiptSink sink); Result maybe_apply( const std::string& user_msg, diff --git a/godbrain_core/cpp_kernel/local_edit_test.cpp b/godbrain_core/cpp_kernel/local_edit_test.cpp index aa95e8d..9e82296 100644 --- a/godbrain_core/cpp_kernel/local_edit_test.cpp +++ b/godbrain_core/cpp_kernel/local_edit_test.cpp @@ -442,6 +442,13 @@ int main() { } } + std::string receipt_body; + int receipt_calls = 0; + local_edit::set_receipt_sink([&](const std::string& body) { + receipt_body = body; + ++receipt_calls; + return std::string(64, 'a'); + }); { std::ofstream out(fixture, std::ios::binary | std::ios::trunc); out << "EDIT_FIXTURE=old\n"; @@ -467,10 +474,23 @@ int main() { !expect(result.before_hash != result.after_hash, "hash moved") || !expect(result.preview_path.find("local_edit_fixture") != std::string::npos, "preview path") || - !expect(!result.rolled_back, "txt apply is not rolled")) { + !expect(!result.rolled_back, "txt apply is not rolled") || + !expect(local_edit::receipt_eligible(result), "passing edit is receipt eligible") || + !expect(receipt_calls == 1, "passing edit mints one receipt") || + !expect(result.receipt_saved, "receipt_saved") || + !expect(result.receipt_id.size() == 64, "receipt_id from sink") || + !expect(receipt_body.find("source_type=edit_receipt") != std::string::npos, + "receipt source_type") || + !expect(receipt_body.find("skill_promote_eligible=false") != + std::string::npos, + "receipt cannot promote") || + !expect(result.report.find("receipt candidate") != std::string::npos, + "report names receipt")) { std::cerr << "hash report=" << result.report << " before=" << result.before_hash.size() - << " after=" << result.after_hash.size() << std::endl; + << " after=" << result.after_hash.size() + << " calls=" << receipt_calls << std::endl; + local_edit::set_receipt_sink({}); return 1; } @@ -510,7 +530,10 @@ int main() { !expect(!result.applied, "bad ps1 not left applied") || !expect(ps1_body == "Write-Output 1\n", "ps1 restored") || !expect(result.report.find("rolled back") != std::string::npos, - "report says rolled back")) { + "report says rolled back") || + !expect(!local_edit::receipt_eligible(result), + "rolled edit is not receipt eligible") || + !expect(!result.receipt_saved, "rolled edit does not mint")) { std::cerr << "rollback report=" << result.report << " body=" << ps1_body << std::endl; return 1; @@ -549,11 +572,17 @@ int main() { !expect(!result.check_ran, "missing verifier did not run") || !expect(body == "EDIT_FIXTURE=old\n", "fixture restored") || !expect(result.report.find("check did not run") != std::string::npos, - "report says check did not run")) { + "report says check did not run") || + !expect(!local_edit::receipt_eligible(result), + "missing verifier is not receipt eligible") || + !expect(!result.receipt_saved, "missing verifier does not mint") || + !expect(receipt_calls == 1, "rollbacks do not mint extra receipts")) { std::cerr << "missing verifier report=" << result.report - << " body=" << body << std::endl; + << " body=" << body << " calls=" << receipt_calls << std::endl; + local_edit::set_receipt_sink({}); return 1; } + local_edit::set_receipt_sink({}); std::cout << "local_edit_test ok" << std::endl; return 0; diff --git a/godbrain_core/cpp_kernel/main.cpp b/godbrain_core/cpp_kernel/main.cpp index 4747529..909993d 100644 --- a/godbrain_core/cpp_kernel/main.cpp +++ b/godbrain_core/cpp_kernel/main.cpp @@ -2115,6 +2115,14 @@ static void handle_last_edit(const httplib::Request&, httplib::Response& res) { reply << " check=" << check_profile << "/" << check_state; } reply << " promote=no"; + const std::string rid = body.value("receipt_id", ""); + if (body.value("receipt_saved", false) && rid.size() >= 12) { + reply << " receipt=" << rid.substr(0, 12); + } else if (body.value("applied", false) && + body.value("check_ok", false) && + !body.value("rolled_back", false)) { + reply << " receipt=none"; + } const std::string pold = body.value("preview_old", ""); const std::string pnew = body.value("preview_new", ""); if (!pold.empty() || !pnew.empty()) { @@ -3638,6 +3646,14 @@ int main() { "until a token is configured in the environment." << std::endl; } + local_edit::set_receipt_sink([](const std::string& body) { + const json stored = memory::save_thought( + {{"content", body}, + {"sector", "local-edit"}, + {"source_type", "edit_receipt"}}); + return stored.value("stable_id", std::string()); + }); + g_frontend_dir = resolve_frontend_dir(); load_oracle_turns(); retry_unstored_oracle_turns();