✨ 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:
1 parent
99a7789432
commit
a39cec5287
15 files changed
+1205
-3
No files matched your search
@@ -0,0 +1,158 @@
|
||||
//! 灵台引擎官方 Rust 绑定(bindgen 生成 + 安全包装,ADR-016/017)。
|
||||
//! 薄封装:句柄 RAII、错误转 `Result`;禁止裸指针外泄(契约 lingtai-ffi 第 8 节)。
|
||||
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
|
||||
use std::ffi::{c_char, c_void, CStr, CString};
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidArgument(String),
|
||||
OutOfMemory,
|
||||
UnsupportedAbi,
|
||||
HandleInvalid,
|
||||
ParseFailed(String),
|
||||
LimitExceeded(String),
|
||||
PolicyDenied(String),
|
||||
Internal(String),
|
||||
Unknown(i32, String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Error::InvalidArgument(m) => write!(f, "参数不合法:{m}"),
|
||||
Error::OutOfMemory => write!(f, "内存分配失败"),
|
||||
Error::UnsupportedAbi => write!(f, "ABI major 不匹配"),
|
||||
Error::HandleInvalid => write!(f, "句柄无效"),
|
||||
Error::ParseFailed(m) => write!(f, "解析失败:{m}"),
|
||||
Error::LimitExceeded(m) => write!(f, "超限:{m}"),
|
||||
Error::PolicyDenied(m) => write!(f, "策略拒绝:{m}"),
|
||||
Error::Internal(m) => write!(f, "内部错误:{m}"),
|
||||
Error::Unknown(code, m) => write!(f, "未知错误 {code}:{m}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
fn map_status(st: HjStatus, message: String) -> Result<(), Error> {
|
||||
match st {
|
||||
HjStatus_HJ_OK => Ok(()),
|
||||
HjStatus_HJ_ERR_INVALID_ARGUMENT => Err(Error::InvalidArgument(message)),
|
||||
HjStatus_HJ_ERR_OUT_OF_MEMORY => Err(Error::OutOfMemory),
|
||||
HjStatus_HJ_ERR_UNSUPPORTED_ABI => Err(Error::UnsupportedAbi),
|
||||
HjStatus_HJ_ERR_HANDLE_INVALID => Err(Error::HandleInvalid),
|
||||
HjStatus_HJ_ERR_PARSE_FAILED => Err(Error::ParseFailed(message)),
|
||||
HjStatus_HJ_ERR_LIMIT_EXCEEDED => Err(Error::LimitExceeded(message)),
|
||||
HjStatus_HJ_ERR_POLICY_DENIED => Err(Error::PolicyDenied(message)),
|
||||
HjStatus_HJ_ERR_INTERNAL => Err(Error::Internal(message)),
|
||||
other => Err(Error::Unknown(other as i32, message)),
|
||||
}
|
||||
}
|
||||
|
||||
fn last_error() -> String {
|
||||
unsafe {
|
||||
let ptr = hj_last_error();
|
||||
if ptr.is_null() {
|
||||
String::new()
|
||||
} else {
|
||||
CStr::from_ptr(ptr).to_string_lossy().into_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 引擎句柄(RAII:Drop 自动释放)。
|
||||
pub struct Engine {
|
||||
ptr: *mut HjEngine,
|
||||
}
|
||||
|
||||
impl Engine {
|
||||
pub fn new() -> Result<Self, Error> {
|
||||
let mut ptr: *mut HjEngine = std::ptr::null_mut();
|
||||
let st = unsafe { hj_engine_new(HJ_ABI_VERSION, &mut ptr) };
|
||||
map_status(st, last_error())?;
|
||||
Ok(Engine { ptr })
|
||||
}
|
||||
|
||||
/// Markdown → canonical ContentBlock JSON。
|
||||
pub fn transform_markdown(&self, input: &str) -> Result<String, Error> {
|
||||
let cin = CString::new(input).map_err(|_| Error::InvalidArgument("含 NUL".into()))?;
|
||||
let mut buf: *mut HjBuffer = std::ptr::null_mut();
|
||||
let st = unsafe {
|
||||
hj_transform_markdown(self.ptr, cin.as_ptr(), cin.as_bytes().len(), &mut buf)
|
||||
};
|
||||
map_status(st, last_error())?;
|
||||
take_buffer(buf)
|
||||
}
|
||||
|
||||
/// 候选重排(确定性)。
|
||||
pub fn recall_rerank(&self, candidates_json: &str) -> Result<String, Error> {
|
||||
let cin =
|
||||
CString::new(candidates_json).map_err(|_| Error::InvalidArgument("含 NUL".into()))?;
|
||||
let mut buf: *mut HjBuffer = std::ptr::null_mut();
|
||||
let st = unsafe { hj_recall_rerank(cin.as_ptr(), &mut buf) };
|
||||
map_status(st, last_error())?;
|
||||
take_buffer(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Engine {
|
||||
fn drop(&mut self) {
|
||||
if !self.ptr.is_null() {
|
||||
unsafe { hj_engine_free(self.ptr) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send/Sync:HjEngine 线程安全(契约第 4 节)
|
||||
unsafe impl Send for Engine {}
|
||||
unsafe impl Sync for Engine {}
|
||||
|
||||
fn take_buffer(buf: *mut HjBuffer) -> Result<String, Error> {
|
||||
if buf.is_null() {
|
||||
return Err(Error::Internal("缓冲为空".into()));
|
||||
}
|
||||
unsafe {
|
||||
let data = hj_buffer_data(buf) as *const c_char;
|
||||
let len = hj_buffer_len(buf);
|
||||
let slice = std::slice::from_raw_parts(data as *const u8, len);
|
||||
let out = String::from_utf8_lossy(slice).into_owned();
|
||||
hj_buffer_free(buf);
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
|
||||
/// URL 协议校验(仅 http/https)。
|
||||
pub fn validate_url(url: &str) -> Result<(), Error> {
|
||||
let cu = CString::new(url).map_err(|_| Error::InvalidArgument("含 NUL".into()))?;
|
||||
let st = unsafe { hj_policy_validate_url(cu.as_ptr()) };
|
||||
map_status(st, last_error())
|
||||
}
|
||||
|
||||
/// 路径约束(授权根内、拒绝 ..)。
|
||||
pub fn validate_path(path: &str, root: &str) -> Result<(), Error> {
|
||||
let cp = CString::new(path).map_err(|_| Error::InvalidArgument("含 NUL".into()))?;
|
||||
let cr = CString::new(root).map_err(|_| Error::InvalidArgument("含 NUL".into()))?;
|
||||
let st = unsafe { hj_policy_validate_path(cp.as_ptr(), cr.as_ptr()) };
|
||||
map_status(st, last_error())
|
||||
}
|
||||
|
||||
/// 决策表(契约 autonomy_level × risk)。
|
||||
pub fn decide(autonomy: i32, risk: i32) -> i32 {
|
||||
unsafe { hj_policy_decide(autonomy, risk) }
|
||||
}
|
||||
|
||||
/// 预算核算。
|
||||
pub fn check_budget(used: i64, maximum: i64) -> Result<(), Error> {
|
||||
let st = unsafe { hj_plan_check_budget(used, maximum) };
|
||||
map_status(st, last_error())
|
||||
}
|
||||
|
||||
/// 解析器版本。
|
||||
pub fn transform_version() -> String {
|
||||
unsafe { CStr::from_ptr(hj_transform_version()).to_string_lossy().into_owned() }
|
||||
}
|
||||
Reference in new issue
Block a user