🎉 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:
chenxin committed 2026-09-09 21:45:04 +08:00
1 parent 5da1efe921
commit 3fbcd0437c
32 files changed
+11459

No files matched your search

+401
View File
@@ -0,0 +1,401 @@
openapi: 3.1.0
info:
title: Hunjing API
version: 0.1.0
summary: Hunjing 平台公共契约(OpenAPI 3.1 单一真源)
license:
name: Apache-2.0
identifier: Apache-2.0
description: |
本文件由 docs/contracts/ 机器化而来,是公共 HTTP 契约的单一真源;
TypeScript/Rust 客户端由本文件生成,不手写重复 DTO。
当前版本覆盖阶段 0 所需基础:错误体、分页、Money、平台 ID、
统一 ContentBlock(21 类型)与流事件 envelope;资源端点随工作包增量登记。
jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema
servers:
- url: https://api.example.dev/v1
description: Native API(开发占位,正式域名见 docs/contracts/api-conventions.md)
paths:
/healthz:
get:
operationId: getHealth
tags: [ops]
summary: 服务健康探针
security: []
responses:
"200":
description: 服务正常
content:
application/json:
schema:
type: object
required: [status]
properties:
status: { type: string, enum: [ok] }
env: { type: string }
default:
description: 错误
content:
application/json:
schema: { $ref: "#/components/schemas/ErrorEnvelope" }
components:
schemas:
# ===== 通用:错误体(api-conventions 第 7 节) =====
Error:
type: object
required: [code, message]
properties:
code:
type: string
description: 稳定机器错误码
examples: [validation_failed]
message: { type: string, description: 可本地化消息 }
trace_id: { type: string, format: uuid }
request_id: { type: string }
details:
type: object
additionalProperties: true
description: 不稳定细节,必须脱敏
ErrorEnvelope:
type: object
required: [error]
properties:
error: { $ref: "#/components/schemas/Error" }
# ===== 通用:分页(opaque cursor) =====
CursorPage:
type: object
required: [data, next_cursor, has_more]
properties:
data: { type: array, items: {} }
next_cursor:
oneOf: [{ type: string }, { type: "null" }]
description: opaque cursor;客户端不得解析
has_more: { type: boolean }
# ===== 通用:金额(最小货币单位整数,禁止浮点) =====
Money:
type: object
required: [amount_minor, currency]
properties:
amount_minor: { type: integer, minimum: 0 }
currency: { type: string, pattern: "^[A-Z]{3}$" }
# ===== 平台 ID(ADR-015:<prefix><lowercase-canonical-uuid>;首批按垂直切片登记) =====
UserId: { type: string, pattern: "^user_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
PersonalDomainId: { type: string, pattern: "^domain_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
ConversationId: { type: string, pattern: "^conversation_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
BranchId: { type: string, pattern: "^branch_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
MessageId: { type: string, pattern: "^message_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
ContentBlockId: { type: string, pattern: "^block_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
RunId: { type: string, pattern: "^run_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
TurnId: { type: string, pattern: "^turn_[0-9a-f]{12}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" }
RequestId: { type: string, pattern: "^request_[0-9a-f]{12}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$", description: 服务端生成,UUIDv4 }
# ===== 统一 ContentBlock(content-blocks-and-streaming 第 2-3 节) =====
BlockStatus:
type: string
enum: [pending, streaming, completed, failed, cancelled]
BlockAnnotation:
type: object
description: 类型化标注(citation/来源/敏感度/语言)
additionalProperties: true
BlockBase:
type: object
required: [id]
properties:
id: { $ref: "#/components/schemas/ContentBlockId" }
status: { $ref: "#/components/schemas/BlockStatus" }
annotations:
type: array
items: { $ref: "#/components/schemas/BlockAnnotation" }
metadata:
type: object
description: 只允许 Schema 登记字段,不存任意秘密
additionalProperties: true
TextBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [text]
properties:
type: { type: string, enum: [text] }
text: { type: string }
language: { type: string }
MarkdownBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [text]
properties:
type: { type: string, enum: [markdown] }
text: { type: string }
flavor: { type: string, enum: [hunjing-gfm-v1] }
CodeBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [source]
properties:
type: { type: string, enum: [code] }
source: { type: string }
language: { type: string }
filename: { type: string }
QuoteBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [content]
properties:
type: { type: string, enum: [quote] }
content:
type: array
items: { $ref: "#/components/schemas/ContentBlock" }
source_ref: { type: string }
TableBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [columns, rows]
properties:
type: { type: string, enum: [table] }
columns: { type: array, items: { type: string } }
rows: { type: array, items: { type: array, items: {} } }
caption: { type: string }
CalloutBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [kind, content]
properties:
type: { type: string, enum: [callout] }
kind: { type: string, enum: [info, warning, danger, success] }
title: { type: string }
content:
type: array
items: { $ref: "#/components/schemas/ContentBlock" }
MathBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [notation, display, source]
properties:
type: { type: string, enum: [math] }
notation: { type: string, enum: [latex, asciimath] }
display: { type: string, enum: [inline, block] }
source: { type: string }
fallback_text: { type: string, description: 可访问性与解析失败降级描述 }
label: { type: string }
MermaidBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [source]
properties:
type: { type: string, enum: [mermaid] }
source: { type: string }
title: { type: string }
caption: { type: string }
fallback_code_language: { type: string, default: mermaid }
ImageBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [alt]
properties:
type: { type: string, enum: [image] }
file_id: { type: string }
url_ref: { type: string, description: "受控 URL;禁止 file://、javascript: 等协议" }
alt: { type: string }
width: { type: integer, minimum: 1 }
height: { type: integer, minimum: 1 }
caption: { type: string }
VideoBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [file_id]
properties:
type: { type: string, enum: [video] }
file_id: { type: string }
poster_file_id: { type: string }
caption: { type: string }
transcript_file_id: { type: string }
duration_ms: { type: integer, minimum: 0 }
AudioBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [file_id]
properties:
type: { type: string, enum: [audio] }
file_id: { type: string }
transcript: { type: string }
duration_ms: { type: integer, minimum: 0 }
waveform: { type: string }
FileBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [file_id, filename, media_type, size_bytes]
properties:
type: { type: string, enum: [file] }
file_id: { type: string }
filename: { type: string }
media_type: { type: string }
size_bytes: { type: integer, minimum: 0 }
description: { type: string }
GalleryBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [items]
properties:
type: { type: string, enum: [gallery] }
items:
type: array
items:
oneOf:
- $ref: "#/components/schemas/ImageBlock"
- $ref: "#/components/schemas/VideoBlock"
layout: { type: string }
AvatarBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [asset_id, format]
properties:
type: { type: string, enum: [avatar] }
asset_id: { type: string }
format: { type: string, enum: [live2d, vrm, gltf] }
preview_file_id: { type: string }
ToolCallBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [tool_name, call_id]
properties:
type: { type: string, enum: [tool_call] }
tool_name: { type: string }
call_id: { type: string }
arguments_redacted: { type: object, description: 参数脱敏视图 }
approval_id: { type: string }
ToolResultBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [call_id]
properties:
type: { type: string, enum: [tool_result] }
call_id: { type: string }
summary: { type: object, additionalProperties: true }
file_ids: { type: array, items: { type: string } }
is_error: { type: boolean }
AgentStatusBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [stage]
properties:
type: { type: string, enum: [agent_status] }
agent_id: { type: string }
child_run_id: { type: string }
stage: { type: string }
progress: { type: object, additionalProperties: true }
waiting_reason: { type: string }
ApprovalRequestBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [approval_id, action]
properties:
type: { type: string, enum: [approval_request] }
approval_id: { type: string }
action: { type: string }
impact: { type: string }
arguments_summary: { type: object, additionalProperties: true }
expires_at: { type: string, format: date-time }
ArtifactBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [artifact_id, artifact_type]
properties:
type: { type: string, enum: [artifact] }
artifact_id: { type: string }
artifact_type: { type: string }
version: { type: integer }
CitationBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [source_id, title, quoted_text]
properties:
type: { type: string, enum: [citation] }
source_id: { type: string }
title: { type: string }
quoted_text: { type: string }
locator:
type: object
description: 定位(文本区间、图像区域、PDF 页区、音视频时间码)与原文快照
additionalProperties: true
ErrorBlock:
allOf:
- $ref: "#/components/schemas/BlockBase"
- type: object
required: [message]
properties:
type: { type: string, enum: [error] }
message: { type: string }
retryable: { type: boolean }
fallback: { type: string }
ContentBlock:
oneOf:
- $ref: "#/components/schemas/TextBlock"
- $ref: "#/components/schemas/MarkdownBlock"
- $ref: "#/components/schemas/CodeBlock"
- $ref: "#/components/schemas/QuoteBlock"
- $ref: "#/components/schemas/TableBlock"
- $ref: "#/components/schemas/CalloutBlock"
- $ref: "#/components/schemas/MathBlock"
- $ref: "#/components/schemas/MermaidBlock"
- $ref: "#/components/schemas/ImageBlock"
- $ref: "#/components/schemas/VideoBlock"
- $ref: "#/components/schemas/AudioBlock"
- $ref: "#/components/schemas/FileBlock"
- $ref: "#/components/schemas/GalleryBlock"
- $ref: "#/components/schemas/AvatarBlock"
- $ref: "#/components/schemas/ToolCallBlock"
- $ref: "#/components/schemas/ToolResultBlock"
- $ref: "#/components/schemas/AgentStatusBlock"
- $ref: "#/components/schemas/ApprovalRequestBlock"
- $ref: "#/components/schemas/ArtifactBlock"
- $ref: "#/components/schemas/CitationBlock"
- $ref: "#/components/schemas/ErrorBlock"
discriminator:
propertyName: type
# ===== 流事件 envelope(content-blocks-and-streaming 第 5 节) =====
StreamEvent:
type: object
required: [type, data]
properties:
type:
type: string
enum:
[
content.block.started,
content.block.delta,
content.block.progress,
content.block.completed,
content.block.failed,
content.block.cancelled,
message.completed,
]
data: { type: object, additionalProperties: true }
sequence:
type: integer
minimum: 0
description: Run 内单调递增
+8
View File
@@ -0,0 +1,8 @@
# Redocly 校验配置(契约工具链见 ADR-025)
extends:
- recommended
rules:
# 契约先行:schemas 随工作包先于端点登记,属正常演进而非死代码。
no-unused-components: off
# /healthz 等运维探针不返回 4xx 业务错误,default 错误响应已覆盖。
operation-4xx-response: off