Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions godbrain_core/cpp_kernel/local_edit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <algorithm>
#include <cctype>
#include <fstream>
#include <functional>
#include <iomanip>
#include <map>
#include <mutex>
Expand All @@ -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 {

Expand Down Expand Up @@ -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) << "\""
Expand Down Expand Up @@ -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<std::mutex> 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");
Expand Down Expand Up @@ -947,6 +980,27 @@ Result maybe_apply(
}
result.report += "\n";
}
if (receipt_eligible(result)) {
ReceiptSink sink;
{
std::lock_guard<std::mutex> 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;
}
Expand Down
6 changes: 6 additions & 0 deletions godbrain_core/cpp_kernel/local_edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<std::string(const std::string& body)>;
void set_receipt_sink(ReceiptSink sink);

Result maybe_apply(
const std::string& user_msg,
Expand Down
39 changes: 34 additions & 5 deletions godbrain_core/cpp_kernel/local_edit_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
16 changes: 16 additions & 0 deletions godbrain_core/cpp_kernel/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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();
Expand Down