✨ feat: 灵台引擎五神齐备与 Go/Rust 官方绑定(WP-15)

- C++ 新增四模块(modules.cpp,19 个导出符号全部 hj_*):
  小周天 hj_stream:流会话(sequence/delta_index 单调)与 content.block.delta envelope 确定性编码(重放字节一致)
  烛照 hj_policy:URL 协议白名单(拒 file/javascript/data)、路径授权根与 .. 段拒绝、幂等键非空≤128、四维决策表(autonomy×risk→allow/ask/deny,非法输入 deny fail-closed)
  洛书 hj_plan:预算核算(达限/负数拒绝)
  河图 hj_recall:确定性重排(词频×稳定排序,canonical JSON 输出;非法输入 PARSE_FAILED)
- doctest 22 用例 20135 断言全绿(含每模块 golden 与确定性双跑)
- Go 绑定(cgo 自包含编译引擎源码):Engine RAII/TransformMarkdown/ValidateURL/ValidatePath/Decide/CheckBudget/RecallRerank;go test 3 用例通过(transform/rerank golden 与 C++ 字节一致)
- Rust 绑定(bindgen 生成 + 安全包装):Engine RAII(Drop 释放)、Error 枚举映射全部 HjStatus、Send/Sync(HjEngine 线程安全);cargo test 3 用例通过(同 golden)
- 三端(C++/Go/Rust)与 Python golden 一致:同一 markdown 输入在四端输出字节相同(跨端一致性首次全语言验证)
This commit is contained in:
chenxin committed 2026-09-12 13:54:12 +08:00
1 parent 99a7789432
commit a39cec5287
15 files changed
+1205 -3

No files matched your search

+106
View File
@@ -0,0 +1,106 @@
// 小周天/烛照/洛书/河图模块测试。
#include <cstring>
#include <string>
#include <doctest/doctest.h>
#include "hunjing/lingtai.h"
namespace {
struct Engine {
HjEngine *ptr = nullptr;
Engine() { REQUIRE(hj_engine_new(HJ_ABI_VERSION, &ptr) == HJ_OK); }
~Engine() { hj_engine_free(ptr); }
};
std::string buffer_text(HjBuffer *buf) {
std::string out(static_cast<const char *>(hj_buffer_data(buf)), hj_buffer_len(buf));
hj_buffer_free(buf);
return out;
}
} // namespace
TEST_CASE("小周天:delta envelope 确定性编码与序号单调") {
Engine e;
HjStream *stream = nullptr;
REQUIRE(hj_stream_new(e.ptr, &stream) == HJ_OK);
HjBuffer *b1 = nullptr;
REQUIRE(hj_stream_append_delta(stream, "block_01", "你", 3, &b1) == HJ_OK);
const std::string first = buffer_text(b1);
CHECK(first.find("\"type\":\"content.block.delta\"") != std::string::npos);
CHECK(first.find("\"sequence\":0") != std::string::npos);
CHECK(first.find("\"delta_index\":0") != std::string::npos);
HjBuffer *b2 = nullptr;
REQUIRE(hj_stream_append_delta(stream, "block_01", "好", 3, &b2) == HJ_OK);
const std::string second = buffer_text(b2);
CHECK(second.find("\"sequence\":1") != std::string::npos);
CHECK(second.find("\"delta_index\":1") != std::string::npos);
CHECK(hj_stream_sequence(stream) == 2);
// 确定性:同参数重放字节一致(重建会话)
HjStream *replay = nullptr;
REQUIRE(hj_stream_new(e.ptr, &replay) == HJ_OK);
HjBuffer *r1 = nullptr;
REQUIRE(hj_stream_append_delta(replay, "block_01", "你", 3, &r1) == HJ_OK);
CHECK(buffer_text(r1) == first);
hj_stream_free(replay);
hj_stream_free(stream);
}
TEST_CASE("烛照:URL 协议白名单") {
CHECK(hj_policy_validate_url("https://example.com/a") == HJ_OK);
CHECK(hj_policy_validate_url("http://example.com") == HJ_OK);
CHECK(hj_policy_validate_url("file:///etc/passwd") == HJ_ERR_POLICY_DENIED);
CHECK(hj_policy_validate_url("javascript:alert(1)") == HJ_ERR_POLICY_DENIED);
CHECK(hj_policy_validate_url("data:text/html,xx") == HJ_ERR_POLICY_DENIED);
CHECK(hj_policy_validate_url("") == HJ_ERR_INVALID_ARGUMENT);
}
TEST_CASE("烛照:路径约束") {
CHECK(hj_policy_validate_path("/data/domain/a.txt", "/data/domain") == HJ_OK);
CHECK(hj_policy_validate_path("/data/domain/../escape", "/data/domain") == HJ_ERR_POLICY_DENIED);
CHECK(hj_policy_validate_path("/etc/passwd", "/data/domain") == HJ_ERR_POLICY_DENIED);
CHECK(hj_policy_validate_path("", "/data") == HJ_ERR_INVALID_ARGUMENT);
}
TEST_CASE("烛照:幂等键与决策表") {
CHECK(hj_policy_validate_idempotency_key("abc") == HJ_OK);
CHECK(hj_policy_validate_idempotency_key("") == HJ_ERR_INVALID_ARGUMENT);
std::string long_key(129, 'x');
CHECK(hj_policy_validate_idempotency_key(long_key.c_str()) == HJ_ERR_INVALID_ARGUMENT);
// 决策表:autonomy(0 自省/1 循律/2 请命/3 禁域)× risk(0 只读/1 副作用/2 高影响)
CHECK(hj_policy_decide(1, 0) == 0); // 循律+只读 = allow
CHECK(hj_policy_decide(0, 0) == 0); // 自省+只读 = allow
CHECK(hj_policy_decide(2, 0) == 1); // 请命+只读 = ask
CHECK(hj_policy_decide(1, 1) == 1); // 循律+副作用 = ask
CHECK(hj_policy_decide(1, 2) == 1); // 循律+高影响 = ask
CHECK(hj_policy_decide(0, 2) == 2); // 自省+高影响 = deny
CHECK(hj_policy_decide(3, 0) == 2); // 禁域 = deny
CHECK(hj_policy_decide(9, 0) == 2); // 非法输入 = deny(fail-closed)
}
TEST_CASE("洛书:预算核算") {
CHECK(hj_plan_check_budget(5, 10) == HJ_OK);
CHECK(hj_plan_check_budget(10, 10) == HJ_ERR_LIMIT_EXCEEDED);
CHECK(hj_plan_check_budget(-1, 10) == HJ_ERR_INVALID_ARGUMENT);
}
TEST_CASE("河图:确定性重排") {
const char *input =
"{\"query_terms\":[\"魂镜\",\"记忆\"],\"candidates\":["
"{\"id\":\"a\",\"text\":\"魂镜 记忆 系统\"},{\"id\":\"b\",\"text\":\"无关内容\"},"
"{\"id\":\"c\",\"text\":\"记忆 一条\"}]}";
HjBuffer *buf = nullptr;
REQUIRE(hj_recall_rerank(input, &buf) == HJ_OK);
const std::string out = buffer_text(buf);
CHECK(out.find("{\"id\":\"a\",\"score\":20}") != std::string::npos);
CHECK(out.find("{\"id\":\"c\",\"score\":10}") != std::string::npos);
CHECK(out.find("{\"id\":\"b\",\"score\":0}") != std::string::npos);
CHECK(out.find("\"a\"") < out.find("\"c\""));
HjBuffer *again = nullptr;
REQUIRE(hj_recall_rerank(input, &again) == HJ_OK);
CHECK(buffer_text(again) == out);
HjBuffer *bad = nullptr;
CHECK(hj_recall_rerank("not json", &bad) == HJ_ERR_PARSE_FAILED);
}