Files
chenxin a39cec5287 ✨ 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 输入在四端输出字节相同(跨端一致性首次全语言验证)
2026-09-12 13:54:12 +08:00

107 lines
4.5 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 小周天/烛照/洛书/河图模块测试。
#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);
}