🎉 feat: 建立阶段 0 工程骨架与 OpenAPI 契约机器化(WP-01 起步)
- Makefile:统一命令入口(backend/engine/docker/openapi/generate,含 make help) - backend:FastAPI + uv 最小工程,统一错误体(契约第 7 节)、ADR-015 平台 ID 生成与校验(uuid-utils);pytest 7 项、ruff、mypy strict、启动冒烟全部通过 - engine:灵台引擎 C++23/CMake 骨架,按 Lingtai C ABI 契约实现 lingtai.h(13 个 HjStatus、版本协商、句柄表防二次释放、线程局部错误上下文);doctest 5 用例通过,nm 验证仅导出 hj_* 符号 - openapi:OpenAPI 3.1 单一真源初始版(错误体/分页/Money/首批平台 ID/ContentBlock 全 21 类型/流事件 envelope),Redocly lint 0 错误、bundle 通过 - frontend:Next.js 最小骨架,openapi-typescript 生成 API 类型并通过 strict tsc - docker:hunjing-postgres(pgvector:17)compose;.env.example 与 config 示例 - ADR-025:登记测试与契约工具链(doctest/libFuzzer/Redocly/openapi-typescript/uuid-utils) - desktop:Tauri v2 占位与初始化说明(WP-10 启动)
This commit is contained in:
1 parent
5da1efe921
commit
3fbcd0437c
32 files changed
+11459
No files matched your search
@@ -0,0 +1,5 @@
|
||||
BasedOnStyle: Google
|
||||
IndentWidth: 2
|
||||
ColumnLimit: 100
|
||||
AccessModifierOffset: -1
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
@@ -0,0 +1,30 @@
|
||||
cmake_minimum_required(VERSION 3.24)
|
||||
|
||||
project(hunjing_lingtai VERSION 0.1.0 LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
# 编译警告全开并视为错误(AGENTS.md 8.4)
|
||||
if(MSVC)
|
||||
add_compile_options(/W4 /WX)
|
||||
else()
|
||||
add_compile_options(-Wall -Wextra -Wpedantic -Wshadow -Wconversion -Wsign-conversion -Werror)
|
||||
endif()
|
||||
|
||||
# 符号默认隐藏,仅导出 hj_*(API 卫生;由 HJ_API 宏显式导出)
|
||||
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
||||
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
|
||||
|
||||
add_library(hunjing SHARED src/lingtai.cpp)
|
||||
target_include_directories(hunjing PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
# 单元测试(doctest,ADR-025);单头直接引入,不用 FetchContent 拉网络
|
||||
add_executable(lingtai_tests tests/test_core.cpp src/lingtai.cpp)
|
||||
target_include_directories(lingtai_tests PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/third_party)
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME lingtai_tests COMMAND lingtai_tests)
|
||||
@@ -0,0 +1,72 @@
|
||||
// Hunjing 灵台引擎公共头(Lingtai C ABI)。
|
||||
// 契约真源:docs/contracts/lingtai-ffi.md;本文件是机器化前的稳定声明基线。
|
||||
// 只导出 extern "C" 的 hj_* 符号与不透明句柄;异常、STL、RTTI 不跨越边界。
|
||||
#ifndef HUNJING_LINGTAI_H
|
||||
#define HUNJING_LINGTAI_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#if defined(HJ_BUILDING)
|
||||
#define HJ_API __declspec(dllexport)
|
||||
#else
|
||||
#define HJ_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define HJ_API __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 当前 ABI major;major 不匹配时 hj_engine_new 拒绝初始化。
|
||||
#define HJ_ABI_VERSION 1
|
||||
|
||||
// 不透明句柄:字段布局不进入契约。
|
||||
typedef struct HjEngine HjEngine;
|
||||
typedef struct HjBuffer HjBuffer;
|
||||
|
||||
// 稳定状态码(登记于契约第 5 节;新增不破坏兼容)。
|
||||
typedef enum HjStatus {
|
||||
HJ_OK = 0,
|
||||
HJ_ERR_INVALID_ARGUMENT = 1,
|
||||
HJ_ERR_OUT_OF_MEMORY = 2,
|
||||
HJ_ERR_UNSUPPORTED_ABI = 3,
|
||||
HJ_ERR_HANDLE_INVALID = 4,
|
||||
HJ_ERR_REENTRANCY = 5,
|
||||
HJ_ERR_PARSE_FAILED = 6,
|
||||
HJ_ERR_LIMIT_EXCEEDED = 7,
|
||||
HJ_ERR_POLICY_DENIED = 8,
|
||||
HJ_ERR_SIGNATURE_INVALID = 9,
|
||||
HJ_ERR_INDEX_CORRUPT = 10,
|
||||
HJ_ERR_BUFFER_TOO_SMALL = 11,
|
||||
HJ_ERR_INTERNAL = 12
|
||||
} HjStatus;
|
||||
|
||||
// 返回引擎构建时的 ABI major。
|
||||
HJ_API uint32_t hj_abi_version(void);
|
||||
|
||||
// 创建引擎句柄;宿主传入所需 major,不匹配返回 HJ_ERR_UNSUPPORTED_ABI 且不产生句柄。
|
||||
// 成功时 *out_engine 非空。HjEngine 线程安全。
|
||||
HJ_API HjStatus hj_engine_new(uint32_t required_abi_major, HjEngine **out_engine);
|
||||
|
||||
// 释放引擎句柄;句柄不在登记表(含二次释放)返回 HJ_ERR_HANDLE_INVALID,不崩溃。
|
||||
HJ_API HjStatus hj_engine_free(HjEngine *engine);
|
||||
|
||||
// 释放引擎分配的缓冲;仅用于引擎产出的 HjBuffer。
|
||||
HJ_API HjStatus hj_buffer_free(HjBuffer *buffer);
|
||||
|
||||
// 读取缓冲内容与长度;非法句柄返回 NULL / 0。
|
||||
HJ_API const void *hj_buffer_data(const HjBuffer *buffer);
|
||||
HJ_API size_t hj_buffer_len(const HjBuffer *buffer);
|
||||
|
||||
// 线程局部的最近一次错误消息(UTF-8、脱敏、不含正文与密钥);下一次调用覆盖。
|
||||
HJ_API const char *hj_last_error(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // HUNJING_LINGTAI_H
|
||||
@@ -0,0 +1,117 @@
|
||||
// 灵台引擎核心(太一):版本协商、句柄表与错误上下文的最小实现。
|
||||
// 全部入口按不可信输入处理;异常不得跨越 ABI 边界。
|
||||
#include "hunjing/lingtai.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
// 不透明句柄的实际布局(不进入契约;仅本翻译单元可见字段)。
|
||||
struct HjEngine {
|
||||
uint32_t abi_major;
|
||||
};
|
||||
|
||||
struct HjBuffer {
|
||||
size_t len;
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
// 线程局部错误消息(脱敏、UTF-8);下一次引擎调用覆盖。
|
||||
thread_local std::string g_last_error;
|
||||
|
||||
void set_last_error(const char *message) { g_last_error = message; }
|
||||
|
||||
// 句柄表:登记活跃引擎句柄,支撑二次释放与非法句柄检测(不崩溃)。
|
||||
std::mutex g_handle_table_mutex;
|
||||
std::unordered_set<HjEngine *> g_handle_table;
|
||||
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
uint32_t hj_abi_version(void) { return HJ_ABI_VERSION; }
|
||||
|
||||
HjStatus hj_engine_new(uint32_t required_abi_major, HjEngine **out_engine) {
|
||||
try {
|
||||
set_last_error("");
|
||||
if (out_engine == nullptr) {
|
||||
set_last_error("out_engine 不能为空");
|
||||
return HJ_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
*out_engine = nullptr;
|
||||
if (required_abi_major != HJ_ABI_VERSION) {
|
||||
set_last_error("ABI major 不匹配:拒绝初始化,不静默降级");
|
||||
return HJ_ERR_UNSUPPORTED_ABI;
|
||||
}
|
||||
auto *engine = new (std::nothrow) HjEngine{required_abi_major};
|
||||
if (engine == nullptr) {
|
||||
set_last_error("引擎内存分配失败");
|
||||
return HJ_ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(g_handle_table_mutex);
|
||||
g_handle_table.insert(engine);
|
||||
}
|
||||
*out_engine = engine;
|
||||
return HJ_OK;
|
||||
} catch (...) {
|
||||
// 入口捕获一切异常并转换为 HjStatus(契约禁令)。
|
||||
set_last_error("引擎内部错误");
|
||||
return HJ_ERR_INTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
HjStatus hj_engine_free(HjEngine *engine) {
|
||||
try {
|
||||
set_last_error("");
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(g_handle_table_mutex);
|
||||
if (g_handle_table.erase(engine) == 0) {
|
||||
set_last_error("句柄不在登记表:已释放或非法");
|
||||
return HJ_ERR_HANDLE_INVALID;
|
||||
}
|
||||
}
|
||||
delete engine;
|
||||
return HJ_OK;
|
||||
} catch (...) {
|
||||
set_last_error("引擎内部错误");
|
||||
return HJ_ERR_INTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
HjStatus hj_buffer_free(HjBuffer *buffer) {
|
||||
try {
|
||||
set_last_error("");
|
||||
if (buffer == nullptr) {
|
||||
set_last_error("buffer 句柄为空");
|
||||
return HJ_ERR_INVALID_ARGUMENT;
|
||||
}
|
||||
free(buffer);
|
||||
return HJ_OK;
|
||||
} catch (...) {
|
||||
set_last_error("引擎内部错误");
|
||||
return HJ_ERR_INTERNAL;
|
||||
}
|
||||
}
|
||||
|
||||
const void *hj_buffer_data(const HjBuffer *buffer) {
|
||||
if (buffer == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<const void *>(buffer + 1);
|
||||
}
|
||||
|
||||
size_t hj_buffer_len(const HjBuffer *buffer) {
|
||||
if (buffer == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return buffer->len;
|
||||
}
|
||||
|
||||
const char *hj_last_error(void) { return g_last_error.c_str(); }
|
||||
|
||||
} // extern "C"
|
||||
@@ -0,0 +1,39 @@
|
||||
// 灵台引擎核心测试(doctest,ADR-025)。
|
||||
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "hunjing/lingtai.h"
|
||||
|
||||
TEST_CASE("ABI 版本协商") {
|
||||
CHECK(hj_abi_version() == HJ_ABI_VERSION);
|
||||
}
|
||||
|
||||
TEST_CASE("引擎创建与释放") {
|
||||
HjEngine *engine = nullptr;
|
||||
REQUIRE(hj_engine_new(HJ_ABI_VERSION, &engine) == HJ_OK);
|
||||
REQUIRE(engine != nullptr);
|
||||
CHECK(hj_engine_free(engine) == HJ_OK);
|
||||
}
|
||||
|
||||
TEST_CASE("major 不匹配时拒绝初始化且不产生句柄") {
|
||||
HjEngine *engine = reinterpret_cast<HjEngine *>(0x1);
|
||||
CHECK(hj_engine_new(HJ_ABI_VERSION + 100, &engine) == HJ_ERR_UNSUPPORTED_ABI);
|
||||
CHECK(engine == nullptr);
|
||||
CHECK(std::string(hj_last_error()).find("ABI") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("非法参数与二次释放不崩溃") {
|
||||
CHECK(hj_engine_new(HJ_ABI_VERSION, nullptr) == HJ_ERR_INVALID_ARGUMENT);
|
||||
CHECK(hj_last_error()[0] != '\0');
|
||||
|
||||
HjEngine *engine = nullptr;
|
||||
REQUIRE(hj_engine_new(HJ_ABI_VERSION, &engine) == HJ_OK);
|
||||
REQUIRE(hj_engine_free(engine) == HJ_OK);
|
||||
CHECK(hj_engine_free(engine) == HJ_ERR_HANDLE_INVALID);
|
||||
}
|
||||
|
||||
TEST_CASE("缓冲防御行为") {
|
||||
CHECK(hj_buffer_free(nullptr) == HJ_ERR_INVALID_ARGUMENT);
|
||||
CHECK(hj_buffer_data(nullptr) == nullptr);
|
||||
CHECK(hj_buffer_len(nullptr) == 0);
|
||||
}
|
||||
Vendored
+7134
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user