@h-ai/cli 0.1.0-alpha.65 → 0.1.0-alpha.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/README.en.md +66 -251
  2. package/README.ja.md +67 -252
  3. package/README.md +66 -251
  4. package/dist/{chunk-47V5OT22.js → chunk-7DSURTJ4.js} +32 -115
  5. package/dist/chunk-7DSURTJ4.js.map +1 -0
  6. package/dist/{chunk-IPG3QNYK.js → chunk-AOQ4N657.js} +212 -8
  7. package/dist/chunk-AOQ4N657.js.map +1 -0
  8. package/dist/cli-config-templates-6AHNUGBN.js +5 -0
  9. package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-6AHNUGBN.js.map} +1 -1
  10. package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-FC3QRKDO.js} +3 -3
  11. package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-FC3QRKDO.js.map} +1 -1
  12. package/dist/index.js +644 -391
  13. package/dist/index.js.map +1 -1
  14. package/dist/{src-6PPBKQ6Q.js → src-2V6VP4I5.js} +972 -520
  15. package/dist/src-2V6VP4I5.js.map +1 -0
  16. package/package.json +8 -8
  17. package/templates/README.md +48 -145
  18. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +1 -1
  19. package/templates/apps/api/.dockerignore.hbs +18 -12
  20. package/templates/apps/api/.gitignore.hbs +9 -1
  21. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  22. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +28 -7
  23. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -3
  24. package/templates/apps/api/hai.build.json.hbs +1 -0
  25. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  26. package/templates/apps/api/package.json.hbs +10 -8
  27. package/templates/apps/fullstack/.dockerignore.hbs +18 -14
  28. package/templates/apps/fullstack/.gitignore.hbs +9 -1
  29. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +23 -8
  30. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  31. package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
  32. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  33. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +3 -3
  34. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +25 -10
  35. package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
  36. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
  37. package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
  38. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
  39. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  40. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -10
  41. package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
  42. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -3
  43. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +12 -1
  44. package/templates/apps/fullstack/hai.build.json.hbs +7 -0
  45. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  46. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  47. package/templates/apps/fullstack/package.json.hbs +7 -12
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  49. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +28 -7
  50. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
  51. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  52. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +33 -12
  53. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +2 -2
  54. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +3 -3
  55. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +2 -2
  56. package/templates/apps/h5/src/routes/+layout.svelte.hbs +2 -2
  57. package/templates/apps/mobile-app/index.html.hbs +1 -0
  58. package/templates/apps/mobile-app/public/runtime-config.js +1 -0
  59. package/templates/apps/mobile-app/src/App.svelte.hbs +2 -2
  60. package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
  61. package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
  62. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  63. package/templates/apps/website/src/routes/+layout.svelte.hbs +3 -3
  64. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  65. package/templates/base/.dockerignore.hbs +18 -13
  66. package/templates/base/.gitignore.hbs +9 -1
  67. package/templates/base/e2e/app.spec.ts.hbs +10 -0
  68. package/templates/base/hai.build.json.hbs +1 -0
  69. package/templates/base/images/app.Dockerfile.hbs +64 -0
  70. package/templates/base/{docker → images/web}/nginx.conf.hbs +6 -0
  71. package/templates/base/package.json.hbs +20 -19
  72. package/templates/base/svelte.config.js.hbs +2 -0
  73. package/templates/base/vite.config.ts.hbs +9 -3
  74. package/templates/shared/.gitattributes +11 -0
  75. package/templates/shared/container-runtime-config.sh +30 -0
  76. package/templates/shared/run-e2e.mjs +65 -0
  77. package/templates/shared/vite-runtime-config.ts +54 -0
  78. package/templates/skills/AGENTS.md +3 -3
  79. package/templates/skills/bridges/admin/AGENTS.md +1 -1
  80. package/templates/skills/bridges/api/AGENTS.md +1 -1
  81. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +1 -1
  82. package/templates/skills/bridges/h5/AGENTS.md +1 -1
  83. package/templates/skills/bridges/mobile-app/AGENTS.md +1 -1
  84. package/templates/skills/bridges/website/AGENTS.md +1 -1
  85. package/templates/skills/hai-api-client/SKILL.md +14 -161
  86. package/templates/skills/hai-api-client/reference.md +166 -0
  87. package/templates/skills/hai-api-contract/SKILL.md +15 -273
  88. package/templates/skills/hai-api-contract/reference.md +278 -0
  89. package/templates/skills/hai-app-create/SKILL.md +2 -2
  90. package/templates/skills/hai-app-create/development.md +6 -4
  91. package/templates/skills/hai-app-create/reference.md +5 -4
  92. package/templates/skills/hai-app-review/SKILL.md +1 -1
  93. package/templates/skills/hai-app-tests/SKILL.md +4 -3
  94. package/templates/skills/hai-app-tests/reference.md +2 -2
  95. package/templates/skills/hai-app-tests/validation.md +31 -0
  96. package/templates/skills/hai-audit/SKILL.md +17 -142
  97. package/templates/skills/hai-audit/reference.md +145 -0
  98. package/templates/skills/hai-build/SKILL.md +24 -3
  99. package/templates/skills/hai-build/reference.md +10 -9
  100. package/templates/skills/hai-cache/SKILL.md +15 -182
  101. package/templates/skills/hai-cache/reference.md +187 -0
  102. package/templates/skills/hai-capacitor/SKILL.md +4 -3
  103. package/templates/skills/hai-capacitor/reference.md +6 -4
  104. package/templates/skills/hai-ci/SKILL.md +2 -0
  105. package/templates/skills/hai-core/SKILL.md +3 -3
  106. package/templates/skills/hai-crypto/SKILL.md +16 -252
  107. package/templates/skills/hai-crypto/reference.md +256 -0
  108. package/templates/skills/hai-datapipe/SKILL.md +15 -196
  109. package/templates/skills/hai-datapipe/reference.md +202 -0
  110. package/templates/skills/hai-deploy/SKILL.md +28 -271
  111. package/templates/skills/hai-deploy/middleware.md +6 -18
  112. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  113. package/templates/skills/hai-framework-sync/sync.md +1 -1
  114. package/templates/skills/hai-iam/SKILL.md +1 -3
  115. package/templates/skills/hai-kit/SKILL.md +12 -167
  116. package/templates/skills/hai-kit/reference.md +172 -0
  117. package/templates/skills/hai-payment/SKILL.md +19 -251
  118. package/templates/skills/hai-payment/reference.md +256 -0
  119. package/templates/skills/hai-pr-review/SKILL.md +4 -4
  120. package/templates/skills/hai-reach/SKILL.md +2 -2
  121. package/templates/skills/hai-reldb/SKILL.md +1 -1
  122. package/templates/skills/hai-scheduler/SKILL.md +15 -195
  123. package/templates/skills/hai-scheduler/reference.md +202 -0
  124. package/templates/skills/hai-serv/SKILL.md +2 -2
  125. package/templates/skills/hai-spec-implement/SKILL.md +1 -1
  126. package/templates/skills/hai-spec-implement/workflow.md +3 -3
  127. package/templates/skills/hai-storage/SKILL.md +17 -238
  128. package/templates/skills/hai-storage/reference.md +243 -0
  129. package/templates/skills/hai-ui/SKILL.md +1 -1
  130. package/templates/skills/hai-ui/reference.md +11 -5
  131. package/templates/skills/hai-vecdb/SKILL.md +2 -2
  132. package/dist/chunk-47V5OT22.js.map +0 -1
  133. package/dist/chunk-IPG3QNYK.js.map +0 -1
  134. package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
  135. package/dist/src-6PPBKQ6Q.js.map +0 -1
  136. package/templates/apps/api/Dockerfile.hbs +0 -48
  137. package/templates/apps/fullstack/Dockerfile.hbs +0 -69
  138. package/templates/base/Dockerfile.hbs +0 -79
  139. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  140. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  141. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: hai-api-contract
3
- description: "使用 @h-ai/api-contract 定义、组合和扩展共享 HTTP contract 与 Zod 输入输出。"
3
+ description: "新增、组合或修改共享 HTTP 接口的路径与输入输出 Schema,供 serv 与 typed client 共用。"
4
4
  ---
5
5
 
6
6
  # hai-api-contract
@@ -15,279 +15,21 @@ description: "使用 @h-ai/api-contract 定义、组合和扩展共享 HTTP cont
15
15
  | 输出 | 供 serv 实现和 api-client 消费的 contract |
16
16
  | 限制 | 纯定义,无 init/close;不包含服务端实现或密钥。业务路径属于 contract,公共传输配置属于 transport。 |
17
17
 
18
- > `@h-ai/api-contract` 是 hai-framework 的公共 HTTP API 契约包,使用 oRPC contract + Zod v4 作为唯一接口真相源。本包只描述接口边界,不包含任何 procedure 实现。
18
+ ## 使用路径
19
19
 
20
- ## 运行环境
20
+ 1. 查当前公开 exports 与已有领域 contract,确认业务路径唯一来源。
21
+ 2. 在 contract 中声明 method/path 与输入输出 Schema;只导出真正跨接口或跨层复用的 Schema。
22
+ 3. 用 `apiContract.create` 组合领域;不把服务实现、私有配置或传输前缀写进 contract。
23
+ 4. 同步 serv procedures、typed client、调用方与失败/校验测试;普通业务输出按当前 HaiResult 契约。
21
24
 
22
- > ✅ **契约定义包,无服务端业务依赖;Zod/oRPC schema 构造仍有运行时代码。** 可在客户端、服务端、测试环境直接引用。
25
+ ## 按需参考
23
26
 
24
- ## 适用场景
27
+ 按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
25
28
 
26
- - 定义新领域的 oRPC contract(输入/输出 Schema + HTTP 路由元数据)
27
- - 组合多个领域 contract 为应用级 contract
28
- - 向 `@h-ai/serv` 提供 contract(装配为 HTTP API)
29
- - 向 `@h-ai/api-client` 提供 contract(生成类型安全客户端)
30
- - 复用内置领域 contract(iam/storage/ai/payment)
31
- - 复用公共 Schema 工厂(`apiContract.haiResultSchema`、分页 Schema)
32
-
33
- ## 使用步骤
34
-
35
- ### 1. 组合应用级 contract
36
-
37
- ```typescript
38
- import { apiContract } from '@h-ai/api-contract'
39
-
40
- // 按需组合,传 false/undefined 的领域不会出现在 contract 或 OpenAPI spec 中
41
- export const myContract = apiContract.create({
42
- iam: apiContract.iam,
43
- storage: apiContract.storage,
44
- ai: apiContract.ai,
45
- payment: false, // 禁用领域
46
- })
47
-
48
- // myContract.iam.auth.login
49
- // myContract.storage.presignedUrls.createUpload
50
- // myContract.ai.chats.createCompletion
51
- ```
52
-
53
- ### 2. 组合自定义 contract
54
-
55
- ```typescript
56
- import { apiContract } from '@h-ai/api-contract'
57
-
58
- export const myContract = apiContract.create({
59
- iam: apiContract.iam,
60
- storage: apiContract.storage,
61
- payment: false, // 禁用领域(不会出现在 contract 或 OpenAPI spec 中)
62
- })
63
- ```
64
-
65
- ### 3. 定义新领域 contract
66
-
67
- ```typescript
68
- import { apiContract } from '@h-ai/api-contract'
69
- import { z } from 'zod'
70
-
71
- // 输入 Schema
72
- const WidgetCreateInputSchema = z.object({
73
- name: z.string().min(1),
74
- description: z.string().optional(),
75
- })
76
-
77
- // 同一 contract 内两个接口复用,因此保留为文件私有常量,不从 schemas 导出。
78
- const WidgetOutputSchema = apiContract.haiResultSchema(z.object({
79
- id: z.string(),
80
- name: z.string(),
81
- createdAt: z.string(),
82
- }))
83
-
84
- // 领域 contract
85
- export const widgetContract = {
86
- widget: {
87
- create: apiContract
88
- .route({
89
- method: 'POST',
90
- path: '/widgets',
91
- operationId: 'widget.create',
92
- summary: 'Create widget',
93
- tags: ['widget'],
94
- })
95
- .input(WidgetCreateInputSchema)
96
- .output(WidgetOutputSchema),
97
-
98
- getById: apiContract
99
- .route({
100
- method: 'GET',
101
- path: '/widgets/{id}',
102
- operationId: 'widget.getById',
103
- summary: 'Get widget by id',
104
- tags: ['widget'],
105
- })
106
- .input(z.object({ id: z.string() }))
107
- .output(WidgetOutputSchema),
108
- },
109
- }
110
- ```
111
-
112
- ### 4. 将新 contract 挂入应用级 contract
113
-
114
- ```typescript
115
- import { apiContract } from '@h-ai/api-contract'
116
- import { widgetContract } from './widget-contract.js'
117
-
118
- export const appContract = apiContract.create({
119
- iam: apiContract.iam,
120
- widget: widgetContract,
121
- })
122
- ```
123
-
124
- ### 5. 传给 `@h-ai/serv`(服务端)
125
-
126
- ```typescript
127
- import { serv } from '@h-ai/serv'
128
- import { appContract } from './app-contract.js'
129
- import { createMyProcedures } from './procedures/widget.js'
130
-
131
- const app = serv.createApp({
132
- contract: appContract,
133
- procedures: {
134
- iam: createIamProcedures({ iam }),
135
- widget: createMyProcedures(),
136
- },
137
- http: { apiPrefix: '/api/v1' },
138
- })
139
- ```
140
-
141
- ## 核心 API
142
-
143
- ### `apiContract.create(options)` — 组合领域 contract
144
-
145
- ```typescript
146
- import { apiContract } from '@h-ai/api-contract'
147
-
148
- const contract = apiContract.create({
149
- iam: apiContract.iam, // 启用:纳入 contract
150
- storage: false, // 禁用:不出现在 OpenAPI spec 中
151
- custom: myContract, // 自定义领域
152
- })
153
- // 类型自动收窄,被禁用的 key 不出现在 contract 类型中
154
- ```
155
-
156
- ### 内置领域 contract
157
-
158
- | 访问路径 | 领域 | 功能概述 |
159
- | --- | --- | --- |
160
- | `apiContract.iam` | IAM | auth(登录/登出/OTP/刷新/注册/改密码)、users、roles、permissions Admin CRUD |
161
- | `apiContract.storage` | Storage | presignedUrls(上传/下载/批量)、files(元数据查询/删除) |
162
- | `apiContract.ai` | AI | chats(completion/流式)、knowledge(上传/查询) |
163
- | `apiContract.payment` | Payment | orders、subscriptions |
164
-
165
- ### 内置 contract 路径示例
166
-
167
- ```typescript
168
- const myContract = apiContract.create({ iam: apiContract.iam, storage: apiContract.storage, ai: apiContract.ai })
169
-
170
- // IAM
171
- myContract.iam.auth.login // POST /auth/login
172
- myContract.iam.auth.logout // POST /auth/logout
173
- myContract.iam.users.list // GET /iam/users
174
- myContract.iam.roles.create // POST /iam/roles
175
-
176
- // Storage
177
- myContract.storage.presignedUrls.createUpload // POST /storage/presigned-urls/upload
178
-
179
- // AI
180
- myContract.ai.chats.createCompletion // POST /ai/chats/completion
181
- ```
182
-
183
- ### `apiContract.haiResultSchema(dataSchema)` — 标准输出包装
184
-
185
- 所有 HTTP 输出必须通过 `apiContract.haiResultSchema` 包装为 `HaiResult<T>`,与 `@h-ai/core` 的 `ok()/err()` 返回结构一致:
186
-
187
- ```typescript
188
- import { apiContract } from '@h-ai/api-contract'
189
- import { z } from 'zod'
190
-
191
- const getWidget = apiContract
192
- .route({ method: 'GET', path: '/widgets/{id}' })
193
- .output(apiContract.haiResultSchema(z.object({
194
- id: z.string(),
195
- name: z.string(),
196
- })))
197
-
198
- // 等价于:
199
- // { success: true, data: { id: string, name: string } }
200
- // | { success: false, error: HaiError }
201
- ```
202
-
203
- ### `apiContract.pathOf(procedure)` — 读取业务路径
204
-
205
- ```typescript
206
- const refreshPath = apiContract.pathOf(apiContract.iam.auth.refresh)
207
- // '/auth/refresh'
208
- ```
209
-
210
- middleware、client 与 E2E 需要业务路径时从 contract 元数据读取,不维护第二份 route 常量。服务端挂载时再与 `_serv.yml` 的 `http.apiPrefix` 拼接。
211
-
212
- ### 公共 Schema 工具
213
-
214
- | 访问路径 | 说明 |
215
- | --- | --- |
216
- | `apiContract.haiResultSchema(dataSchema)` | `HaiResult<T>` 输出包装 |
217
- | `apiContract.voidResultSchema` | `HaiResult<void>` 空结果包装 |
218
- | `apiContract.paginatedSchema(itemSchema)` | 分页列表输出 Schema |
219
- | `HaiErrorSchema` | HaiError 公共字段 Schema |
220
- | `PaginationQuerySchema` | 分页参数 Schema(可选 page/pageSize,pageSize 上限 100;默认值由服务端处理) |
221
-
222
- ## Contract 定义规范
223
-
224
- ### 必须遵循
225
-
226
- - **输出必须包装为 `apiContract.haiResultSchema`** — 客户端依赖 `success` 判断成功/失败
227
- - **业务 path 直接写在对应 `*-contract.ts`** — 禁止新增 `*-routes.ts` 或业务路径常量
228
- - **一次性 `*OutputSchema` 直接内联** — 不在 schemas 文件声明或导出
229
- - **同一 contract 内重复使用的输出可设私有常量** — 不得为复用扩大公共导出
230
- - **公共 HTTP 传输配置以配置文件为准** — API 前缀、健康检查、OpenAPI/docs、密钥协商路径与跨端 Header 放在 service 的 `_serv.yml`,不放 contract 包
231
- - **schemas 只保留真实复用结构** — 至少被多个接口或 contract/procedure 等不同层共同消费
232
- - **`operationId` 全局唯一** — 格式为 `domain.resource.action`,如 `iam.auth.login`
233
- - **`tags` 至少包含领域名** — 供 OpenAPI 文档分组
234
- - **输入 Schema 用 Zod v4** — 与 `@orpc/zod/zod4` 的 `ZodToJsonSchemaConverter` 配套
235
- - **Contract 不含业务逻辑** — 只有 Schema + 路由元数据,没有 `async function`
236
-
237
- ### 禁止
238
-
239
- - ❌ Contract 文件中 `import` 任何业务模块(`@h-ai/iam`、`@h-ai/storage` 等)
240
- - ❌ 在 contract 中使用 `z.any()` 或 `z.unknown()` 作为输出类型
241
- - ❌ 重复定义已存在的领域 contract
242
- - ❌ 导出只被单个 contract 接口使用的 `*OutputSchema`
243
- - ❌ 用独立 `*-routes.ts` 维护业务接口路径
244
-
245
- ### 文件职责
246
-
247
- | 文件 | 只负责 |
248
- | --- | --- |
249
- | `*-contract.ts` | 业务接口 path、method、输入与输出边界 |
250
- | `*-schemas.ts` | 跨接口或跨层复用的输入、实体、数据结构 |
251
- | service `config/_serv.yml` | API 前缀、基础设施端点、跨端 Header 与加密 transport 配置 |
252
-
253
- ## 常见模式
254
-
255
- ### 带分页的列表接口
256
-
257
- ```typescript
258
- import { apiContract, PaginationQuerySchema } from '@h-ai/api-contract'
259
- import { z } from 'zod'
260
-
261
- const WidgetSchema = z.object({ id: z.string(), name: z.string() })
262
-
263
- const listWidgets = apiContract
264
- .route({ method: 'GET', path: '/widgets', operationId: 'widget.list', tags: ['widget'] })
265
- .input(PaginationQuerySchema.extend({ keyword: z.string().optional() }))
266
- .output(apiContract.haiResultSchema(apiContract.paginatedSchema(WidgetSchema)))
267
- ```
268
-
269
- ### 路径参数
270
-
271
- ```typescript
272
- // 路径中的 {id} 对应 input 中的 id 字段
273
- const getWidget = apiContract
274
- .route({ method: 'GET', path: '/widgets/{id}', operationId: 'widget.getById', tags: ['widget'] })
275
- .input(z.object({ id: z.string() }))
276
- .output(apiContract.haiResultSchema(WidgetSchema))
277
- ```
278
-
279
- ### 无输入的端点
280
-
281
- ```typescript
282
- const versionContract = apiContract
283
- .route({ method: 'GET', path: '/system/version', operationId: 'system.version', tags: ['system'] })
284
- .output(apiContract.haiResultSchema(z.object({ version: z.string() })))
285
- ```
286
-
287
- ## 测试
288
-
289
- ```bash
290
- pnpm --filter @h-ai/api-contract test
291
- pnpm --filter @h-ai/api-contract typecheck
292
- pnpm --filter @h-ai/api-contract lint
293
- ```
29
+ - [运行环境](reference.md#运行环境)
30
+ - [适用场景](reference.md#适用场景)
31
+ - [使用步骤](reference.md#使用步骤)
32
+ - [核心 API](reference.md#核心-api)
33
+ - [Contract 定义规范](reference.md#contract-定义规范)
34
+ - [常见模式](reference.md#常见模式)
35
+ - [测试](reference.md#测试)
@@ -0,0 +1,278 @@
1
+ # hai-api-contract 参考
2
+
3
+ > `@h-ai/api-contract` 是 hai-framework 的公共 HTTP API 契约包,使用 oRPC contract + Zod v4 作为唯一接口真相源。本包只描述接口边界,不包含任何 procedure 实现。
4
+
5
+ ## 运行环境
6
+
7
+ > ✅ **契约定义包,无服务端业务依赖;Zod/oRPC schema 构造仍有运行时代码。** 可在客户端、服务端、测试环境直接引用。
8
+
9
+ ## 适用场景
10
+
11
+ - 定义新领域的 oRPC contract(输入/输出 Schema + HTTP 路由元数据)
12
+ - 组合多个领域 contract 为应用级 contract
13
+ - 向 `@h-ai/serv` 提供 contract(装配为 HTTP API)
14
+ - 向 `@h-ai/api-client` 提供 contract(生成类型安全客户端)
15
+ - 复用内置领域 contract(iam/storage/ai/payment)
16
+ - 复用公共 Schema 工厂(`apiContract.haiResultSchema`、分页 Schema)
17
+
18
+ ## 使用步骤
19
+
20
+ ### 1. 组合应用级 contract
21
+
22
+ ```typescript
23
+ import { apiContract } from '@h-ai/api-contract'
24
+
25
+ // 按需组合,传 false/undefined 的领域不会出现在 contract 或 OpenAPI spec 中
26
+ export const myContract = apiContract.create({
27
+ iam: apiContract.iam,
28
+ storage: apiContract.storage,
29
+ ai: apiContract.ai,
30
+ payment: false, // 禁用领域
31
+ })
32
+
33
+ // myContract.iam.auth.login
34
+ // myContract.storage.presignedUrls.createUpload
35
+ // myContract.ai.chats.createCompletion
36
+ ```
37
+
38
+ ### 2. 组合自定义 contract
39
+
40
+ ```typescript
41
+ import { apiContract } from '@h-ai/api-contract'
42
+
43
+ export const myContract = apiContract.create({
44
+ iam: apiContract.iam,
45
+ storage: apiContract.storage,
46
+ payment: false, // 禁用领域(不会出现在 contract 或 OpenAPI spec 中)
47
+ })
48
+ ```
49
+
50
+ ### 3. 定义新领域 contract
51
+
52
+ ```typescript
53
+ import { apiContract } from '@h-ai/api-contract'
54
+ import { z } from 'zod'
55
+
56
+ // 输入 Schema
57
+ const WidgetCreateInputSchema = z.object({
58
+ name: z.string().min(1),
59
+ description: z.string().optional(),
60
+ })
61
+
62
+ // 同一 contract 内两个接口复用,因此保留为文件私有常量,不从 schemas 导出。
63
+ const WidgetOutputSchema = apiContract.haiResultSchema(z.object({
64
+ id: z.string(),
65
+ name: z.string(),
66
+ createdAt: z.string(),
67
+ }))
68
+
69
+ // 领域 contract
70
+ export const widgetContract = {
71
+ widget: {
72
+ create: apiContract
73
+ .route({
74
+ method: 'POST',
75
+ path: '/widgets',
76
+ operationId: 'widget.create',
77
+ summary: 'Create widget',
78
+ tags: ['widget'],
79
+ })
80
+ .input(WidgetCreateInputSchema)
81
+ .output(WidgetOutputSchema),
82
+
83
+ getById: apiContract
84
+ .route({
85
+ method: 'GET',
86
+ path: '/widgets/{id}',
87
+ operationId: 'widget.getById',
88
+ summary: 'Get widget by id',
89
+ tags: ['widget'],
90
+ })
91
+ .input(z.object({ id: z.string() }))
92
+ .output(WidgetOutputSchema),
93
+ },
94
+ }
95
+ ```
96
+
97
+ ### 4. 将新 contract 挂入应用级 contract
98
+
99
+ ```typescript
100
+ import { apiContract } from '@h-ai/api-contract'
101
+ import { widgetContract } from './widget-contract.js'
102
+
103
+ export const appContract = apiContract.create({
104
+ iam: apiContract.iam,
105
+ widget: widgetContract,
106
+ })
107
+ ```
108
+
109
+ ### 5. 传给 `@h-ai/serv`(服务端)
110
+
111
+ ```typescript
112
+ import { serv } from '@h-ai/serv'
113
+ import { appContract } from './app-contract.js'
114
+ import { createMyProcedures } from './procedures/widget.js'
115
+
116
+ const app = serv.createApp({
117
+ contract: appContract,
118
+ procedures: {
119
+ iam: createIamProcedures({ iam }),
120
+ widget: createMyProcedures(),
121
+ },
122
+ http: { apiPrefix: '/api/v1' },
123
+ })
124
+ ```
125
+
126
+ ## 核心 API
127
+
128
+ ### `apiContract.create(options)` — 组合领域 contract
129
+
130
+ ```typescript
131
+ import { apiContract } from '@h-ai/api-contract'
132
+
133
+ const contract = apiContract.create({
134
+ iam: apiContract.iam, // 启用:纳入 contract
135
+ storage: false, // 禁用:不出现在 OpenAPI spec 中
136
+ custom: myContract, // 自定义领域
137
+ })
138
+ // 类型自动收窄,被禁用的 key 不出现在 contract 类型中
139
+ ```
140
+
141
+ ### 内置领域 contract
142
+
143
+ | 访问路径 | 领域 | 功能概述 |
144
+ | --- | --- | --- |
145
+ | `apiContract.iam` | IAM | auth(登录/登出/OTP/刷新/注册/改密码)、users、roles、permissions Admin CRUD |
146
+ | `apiContract.storage` | Storage | presignedUrls(上传/下载/批量)、files(元数据查询/删除) |
147
+ | `apiContract.ai` | AI | chats(completion/流式)、knowledge(上传/查询) |
148
+ | `apiContract.payment` | Payment | orders、subscriptions |
149
+
150
+ ### 内置 contract 路径示例
151
+
152
+ ```typescript
153
+ const myContract = apiContract.create({ iam: apiContract.iam, storage: apiContract.storage, ai: apiContract.ai })
154
+
155
+ // IAM
156
+ myContract.iam.auth.login // POST /auth/login
157
+ myContract.iam.auth.logout // POST /auth/logout
158
+ myContract.iam.users.list // GET /iam/users
159
+ myContract.iam.roles.create // POST /iam/roles
160
+
161
+ // Storage
162
+ myContract.storage.presignedUrls.createUpload // POST /storage/presigned-urls/upload
163
+
164
+ // AI
165
+ myContract.ai.chats.createCompletion // POST /ai/chats/completion
166
+ ```
167
+
168
+ ### `apiContract.haiResultSchema(dataSchema)` — 标准输出包装
169
+
170
+ 所有 HTTP 输出必须通过 `apiContract.haiResultSchema` 包装为 `HaiResult<T>`,与 `@h-ai/core` 的 `ok()/err()` 返回结构一致:
171
+
172
+ ```typescript
173
+ import { apiContract } from '@h-ai/api-contract'
174
+ import { z } from 'zod'
175
+
176
+ const getWidget = apiContract
177
+ .route({ method: 'GET', path: '/widgets/{id}' })
178
+ .output(apiContract.haiResultSchema(z.object({
179
+ id: z.string(),
180
+ name: z.string(),
181
+ })))
182
+
183
+ // 等价于:
184
+ // { success: true, data: { id: string, name: string } }
185
+ // | { success: false, error: HaiError }
186
+ ```
187
+
188
+ ### `apiContract.pathOf(procedure)` — 读取业务路径
189
+
190
+ ```typescript
191
+ const refreshPath = apiContract.pathOf(apiContract.iam.auth.refresh)
192
+ // '/auth/refresh'
193
+ ```
194
+
195
+ middleware、client 与 E2E 需要业务路径时从 contract 元数据读取,不维护第二份 route 常量。服务端挂载时再与 `_serv.yml` 的 `http.apiPrefix` 拼接。
196
+
197
+ ### 公共 Schema 工具
198
+
199
+ | 访问路径 | 说明 |
200
+ | --- | --- |
201
+ | `apiContract.haiResultSchema(dataSchema)` | `HaiResult<T>` 输出包装 |
202
+ | `apiContract.voidResultSchema` | `HaiResult<void>` 空结果包装 |
203
+ | `apiContract.paginatedSchema(itemSchema)` | 分页列表输出 Schema |
204
+ | `HaiErrorSchema` | HaiError 公共字段 Schema |
205
+ | `PaginationQuerySchema` | 分页参数 Schema(可选 page/pageSize,pageSize 上限 100;默认值由服务端处理) |
206
+
207
+ ## Contract 定义规范
208
+
209
+ ### 必须遵循
210
+
211
+ - **输出必须包装为 `apiContract.haiResultSchema`** — 客户端依赖 `success` 判断成功/失败
212
+ - **业务 path 直接写在对应 `*-contract.ts`** — 禁止新增 `*-routes.ts` 或业务路径常量
213
+ - **一次性 `*OutputSchema` 直接内联** — 不在 schemas 文件声明或导出
214
+ - **同一 contract 内重复使用的输出可设私有常量** — 不得为复用扩大公共导出
215
+ - **公共 HTTP 传输配置以配置文件为准** — API 前缀、健康检查、OpenAPI/docs、密钥协商路径与跨端 Header 放在 service 的 `_serv.yml`,不放 contract 包
216
+ - **schemas 只保留真实复用结构** — 至少被多个接口或 contract/procedure 等不同层共同消费
217
+ - **`operationId` 全局唯一** — 格式为 `domain.resource.action`,如 `iam.auth.login`
218
+ - **`tags` 至少包含领域名** — 供 OpenAPI 文档分组
219
+ - **输入 Schema 用 Zod v4** — 与 `@orpc/zod/zod4` 的 `ZodToJsonSchemaConverter` 配套
220
+ - **Contract 不含业务逻辑** — 只有 Schema + 路由元数据,没有 `async function`
221
+
222
+ ### 禁止
223
+
224
+ - ❌ Contract 文件中 `import` 任何业务模块(`@h-ai/iam`、`@h-ai/storage` 等)
225
+ - ❌ 在 contract 中使用 `z.any()` 或 `z.unknown()` 作为输出类型
226
+ - ❌ 重复定义已存在的领域 contract
227
+ - ❌ 导出只被单个 contract 接口使用的 `*OutputSchema`
228
+ - ❌ 用独立 `*-routes.ts` 维护业务接口路径
229
+
230
+ ### 文件职责
231
+
232
+ | 文件 | 只负责 |
233
+ | --- | --- |
234
+ | `*-contract.ts` | 业务接口 path、method、输入与输出边界 |
235
+ | `*-schemas.ts` | 跨接口或跨层复用的输入、实体、数据结构 |
236
+ | service `config/_serv.yml` | API 前缀、基础设施端点、跨端 Header 与加密 transport 配置 |
237
+
238
+ ## 常见模式
239
+
240
+ ### 带分页的列表接口
241
+
242
+ ```typescript
243
+ import { apiContract, PaginationQuerySchema } from '@h-ai/api-contract'
244
+ import { z } from 'zod'
245
+
246
+ const WidgetSchema = z.object({ id: z.string(), name: z.string() })
247
+
248
+ const listWidgets = apiContract
249
+ .route({ method: 'GET', path: '/widgets', operationId: 'widget.list', tags: ['widget'] })
250
+ .input(PaginationQuerySchema.extend({ keyword: z.string().optional() }))
251
+ .output(apiContract.haiResultSchema(apiContract.paginatedSchema(WidgetSchema)))
252
+ ```
253
+
254
+ ### 路径参数
255
+
256
+ ```typescript
257
+ // 路径中的 {id} 对应 input 中的 id 字段
258
+ const getWidget = apiContract
259
+ .route({ method: 'GET', path: '/widgets/{id}', operationId: 'widget.getById', tags: ['widget'] })
260
+ .input(z.object({ id: z.string() }))
261
+ .output(apiContract.haiResultSchema(WidgetSchema))
262
+ ```
263
+
264
+ ### 无输入的端点
265
+
266
+ ```typescript
267
+ const versionContract = apiContract
268
+ .route({ method: 'GET', path: '/system/version', operationId: 'system.version', tags: ['system'] })
269
+ .output(apiContract.haiResultSchema(z.object({ version: z.string() })))
270
+ ```
271
+
272
+ ## 测试
273
+
274
+ ```bash
275
+ pnpm --filter @h-ai/api-contract test
276
+ pnpm --filter @h-ai/api-contract typecheck
277
+ pnpm --filter @h-ai/api-contract lint
278
+ ```
@@ -28,13 +28,13 @@ description: "在 hai 应用中新增、修改或修复功能,按需求复用
28
28
  1. 读取 [development.md](development.md):先按真实需求匹配框架能力、已安装版本与应用架构,再确定改动。适用能力包括同一模块已有 feature、配置和扩展点,不以导入包作为复用完成的证据。
29
29
  2. 读取所选模块的 skill 与实际公开类型,复用现有服务和组件。新增依赖或直接使用第三方 SDK 前说明已有能力的具体缺口;无需安装无关模块。
30
30
  3. 新增或改变行为时用有意义的目标用例验证,仍适用的回归保持通过;被明确新需求替代的模板断言同步更新并记录依据。按下方参考实现,使用 [hai-app-review](../hai-app-review/SKILL.md) 检查改动涉及的安全与质量边界。
31
- 4. 运行当前项目已定义的门禁,报告行为证据、能力选择及未验证项。纯文案、格式和文档变化按影响验证,不强制新增测试。
31
+ 4. 按 [分轮验证与集中修复](../hai-app-tests/validation.md) 运行已有门禁:一轮先收集所有可执行结果,再归并修复、复测失败及受影响项;报告行为证据、能力选择及未验证项。纯文案、格式和文档变化按影响验证,不强制新增测试。
32
32
 
33
33
  ## 按需参考
34
34
 
35
35
  新增新手引导或帮助说明时读取 [UI 引导指南](../hai-ui/guide.md),复用 fullstack 默认的共享 guide-store、locale 和稳定 data-guide 锚点。
36
36
 
37
- 详细配置、API 签名、错误码和范本在 [reference.md](reference.md)。按下列标题定位所需小节,不默认通读;用例中的业务变量需结合当前应用补齐。
37
+ 按任务读取 [reference.md](reference.md) 的对应小节;签名以当前源码或安装版本的公开类型为准,不默认加载全部资料。示例中的业务变量由应用提供。
38
38
 
39
39
  - 适用场景
40
40
  - 行为开发与验证
@@ -48,14 +48,14 @@ YAML 使用 `${HAI_DB_TYPE:sqlite}` 等显式引用,冒号后是未配置时
48
48
 
49
49
  | 入口 | 开发 | 测试环境 | 生产环境 |
50
50
  | ---------------- | ---------- | ------------------------ | --------------------------------------------- |
51
- | Vite / SvelteKit | `pnpm dev` | `pnpm dev:test` | `pnpm build:prod` 或 `pnpm dev:prod` 预览配置 |
51
+ | Vite / SvelteKit | `pnpm dev` | `pnpm dev:test` | `pnpm dev:prod` 预览;build 不选择环境 |
52
52
  | Node service | `pnpm dev` | 构建后 `pnpm start:test` | 构建后 `pnpm start:prod` |
53
53
 
54
54
  所有入口优先级一致:**进程环境 > `.env.local` > `.env.<dev|test|prod>` > `.env`**。Vite 配置在 Node 阶段通过 `core.config.loadEnvFiles({ mode, envDir })` 读取,检查 `success` 后注入进程;`development`/`production` 分别映射到 `dev`/`prod`。Node 服务按 `.env`、环境文件、`.env.local` 的顺序传入 `--env-file`,后者覆盖前者,已有进程环境最高。`core.init` 只读取已注入的变量,不自行读取 dotenv。
55
55
 
56
56
  `prod` 是文件模式名,对应 `HAI_CORE_ENV=production`;环境枚举为 `development | test | production | staging`。SvelteKit adapter-node 产物使用 `node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local build/index.js`。文件路径相对进程工作目录,公共文件随项目提交;容器通过部署平台或 `--env-file` 注入目标环境值。浏览器只接收公开白名单,构建产物和镜像不包含 `.env.local`。
57
57
 
58
- Fullstack 的环境文件在工程根;service 脚本通过 `../../.env*` 加载,Web/App/Desktop 的 Vite `envDir` 指向根目录。API workspace 的环境文件在 `apps/<name>-service/`。独立应用在各自根目录加载。浏览器配置在构建期注入时,改环境参数后须重新构建;fullstack 的公开应用元数据由 service `app.info` 返回。
58
+ Fullstack 的环境文件在工程根;service 脚本通过 `../../.env*` 加载,Web/App/Desktop 的 Vite 自动 dotenv 加载关闭(`envDir: false`),开发和预览由 `core.config.loadEnvFiles` 从工程根读取,构建不读取部署环境文件。API workspace 的环境文件在 `apps/<name>-service/`。独立应用在各自根目录加载。静态 Web 通过 `runtime-config.js` 获取公开地址,部署环境覆盖无需重建镜像;原生包在复制静态资源前配置该入口。修改编译进客户端的默认值时仍需重新构建;fullstack 的公开应用元数据由 service `app.info` 返回。
59
59
 
60
60
  #### 迁移已有项目
61
61
 
@@ -125,7 +125,7 @@ hai create my-app
125
125
 
126
126
  ## 将需求映射到现有能力
127
127
 
128
- 应用元信息只有一份配置来源:名称、版本、环境、默认语言与日志设置来自 `_core.yml`,使用 `core.config` 与 `core.logger`;Node 启动时不通过固定 `logging` 覆盖 YAML。SvelteKit 服务端 layout 和 fullstack `app.info` 仅公开必要元信息,不能下发整个 core 或模块配置。独立 Vite mobile-app 在 Node 构建阶段经 core 加载 YAML/环境覆盖并注入公开字段,配置变化需重新构建。环境覆盖使用 `HAI_CORE_NAME`、`HAI_CORE_VERSION`、`HAI_CORE_ENV`、`HAI_CORE_LOGGING_LEVEL` 等既有规则;容器明确配置生产环境,不能用另一个状态来源代替 `core.env`。
128
+ 应用元信息只有一份配置来源:名称、版本、环境、默认语言与日志设置来自 `_core.yml`,使用 `core.config` 与 `core.logger`;Node 启动时不通过固定 `logging` 覆盖 YAML。SvelteKit 服务端 layout 和 fullstack `app.info` 仅公开必要元信息,不能下发整个 core 或模块配置。独立 Vite 客户端经 Node/core 加载 YAML 默认值,Web 部署通过 `runtime-config.js` 应用公开白名单覆盖;只修改部署覆盖无需重建,修改构建默认值则须重新构建。环境覆盖使用 `HAI_CORE_NAME`、`HAI_CORE_VERSION`、`HAI_CORE_ENV`、`HAI_CORE_LOGGING_LEVEL` 等既有规则;容器明确配置生产环境,不能用另一个状态来源代替 `core.env`。
129
129
 
130
130
  开发前,对本次涉及的需求简述“需求 → 现有应用实现/框架能力 → 已安装版本与公开契约依据 → 最小业务补充/修改位置 → 成功、权限与失败验收”。简单任务可用几句话;跨模块任务用表格。查找顺序:已有应用实现 → 框架公开能力和配置 → 最小业务补充。
131
131
 
@@ -209,7 +209,9 @@ export function pageOffset(query: PageQuery): number {
209
209
 
210
210
  ## 验证与交付
211
211
 
212
+ 测试临时工程、镜像、容器、辅助进程与数据须在 finally/teardown 自动清理,成功、失败和取消均执行;只清理本次创建的资源。日志、截图与报告保留到分析和交付结束再清理,不提交验收产物。
213
+
212
214
  - 对新增/改变行为按 [hai-app-tests](../hai-app-tests/SKILL.md) 写有意义的测试;目标用例先因行为缺失失败,既有回归保持通过。UI/路由改动覆盖真实流程、异步失败与权限路径;纯文案/文档按影响验证。
213
- - 从当前 `package.json` 选择实际脚本,优先 workspace filter;按 `typecheck → lint → build(构建/配置/跨包变更)→ test → 相关 E2E` 验证,PowerShell 使用 `pnpm.cmd`。不能调用框架仓库私有命令替代应用检查。
215
+ - 从当前 `package.json` 选择实际脚本,优先 workspace filter,PowerShell 使用 `pnpm.cmd`。多项门禁按 [分轮验证与集中修复](../hai-app-tests/validation.md) 先脚本化收集结果和日志,再集中修复、复测失败及受影响项;普通失败不立即从头重跑。不能调用框架仓库私有命令替代应用检查。
214
216
  - typecheck/lint 只覆盖部分质量规则;权限、事务、降级、真实 provider、浏览器、容器和原生交付各自需要适用行为证据。缺环境或密钥时报告未验证,不能用 mock 冒充真实集成。
215
217
  - 完成说明:实际复用能力与自定义缺口、修改路径、已执行命令/行为结果、同步的 contract/调用方/配置/i18n/文档、未验证项及原因。无需无关模块的“覆盖率”清单。
@@ -40,7 +40,7 @@
40
40
  ### Step 4:重构与验证(Refactor 阶段)
41
41
 
42
42
  1. 参照 `hai-app-review` 审查代码规范
43
- 2. 依当前 scripts 顺序执行 `typecheck → lint → build(构建/配置/跨包变更)→ test`,优先 workspace filter,PowerShell 使用 `pnpm.cmd`
43
+ 2. 按 [分轮验证与集中修复](../hai-app-tests/validation.md) 收集适用门禁,普通失败不中断独立检查,汇总后集中修复、再验证;优先 workspace filter,PowerShell 使用 `pnpm.cmd`
44
44
  3. UI/路由/端到端改动执行相应 E2E;报告失败路径与安全验收结果,未运行的边界说明原因
45
45
 
46
46
  ## 目录结构约定
@@ -334,13 +334,14 @@ core.init() → crypto.init() → reldb.init() → cache.init() → iam.init()
334
334
 
335
335
  仅涉及容器构建/部署变更时使用本节;普通功能开发无需每次启动镜像。
336
336
 
337
- - 保持脚手架根目录 `Dockerfile`、`.dockerignore` 与 `package.json` 中 `docker:build*` / `podman:build*` 命令同步。
337
+ - 保持脚手架 `images/*.Dockerfile`、`.dockerignore` 与 `package.json` 中 `build` / `compile` 命令与 hai.build.json同步。保留 `.gitattributes`,让 `.dockerignore`、Dockerfile 和 POSIX 脚本统一使用 LF,保持跨平台输入一致并确保 Linux 脚本可运行。
338
338
  - Podman 多阶段构建保持 `--format docker --jobs=1`;前者保留 `HEALTHCHECK`,后者避免部分 Podman 5.8/Buildah 组合在并行 stage 构建时触发内部 semaphore panic。
339
- - 先复制 lockfile 与 workspace 配置执行 `pnpm fetch`,再复制源码并离线安装、构建;避免源码变化重复下载依赖,同时兼容新增 workspace。
339
+ - 先复制 lockfile、workspace 配置与构建器准备的 `images/dependencies/` 安装输入执行 `pnpm fetch --ignore-scripts`,再复制源码并离线安装、构建;fetch、安装和 `pnpm pm deploy` 共用 pnpm store,安装与生产依赖整理共用 SQLite 驱动缓存,避免重复下载,兼容新增 workspace、本地归档与补丁。
340
+ - 默认容器面向 CPU 运行或远程 AI 服务,Dockerfile 设置 `ONNXRUNTIME_NODE_INSTALL_CUDA=skip`,跳过额外 CUDA/TensorRT 下载;使用宿主机 GPU 时定制镜像和运行参数,不修改本机 npm 或全局 AI/GPU 配置。
340
341
  - SvelteKit 服务使用 Node adapter;纯 Vite 前端使用带 SPA fallback 的静态镜像;fullstack 的 Service 与 Web 分别交付,Service 直接监听且不增加转发进程。
341
342
  - Web 的 `PUBLIC_*` 部署配置在容器启动时生成静态运行时配置,不作为 build arg 烘焙,确保同一镜像可跨环境部署。
342
343
  - 配置文件随镜像提供,并允许只读挂载覆盖;密钥只通过环境变量或 `--env-file` 注入。
343
- - 普通构建不携带 `data/`;只有用户明确运行 `docker:build:data` / `podman:build:data` 时才通过 named build context 嵌入 SQLite/本地存储初始快照。
344
+ - 普通构建不携带 `data/`;持续写入使用运行期 volume;需要预置数据时显式维护独立 Dockerfile 和明确的 build context,不把本机数据加入默认镜像。
344
345
  - 数据库和本地存储的持续写入必须落到 volume,不能依赖镜像可写层。
345
346
  - 完成前实际启动镜像,验证健康端点、非 root 用户、环境变量覆盖、配置挂载和预置数据读取。
346
347
 
@@ -72,7 +72,7 @@ typecheck/lint 不能证明越权、租户隔离、事务、错误状态或资
72
72
 
73
73
  ## 门禁与输出
74
74
 
75
- 从当前 package.json 选择实际 scripts,优先 workspace filter;PowerShell 使用 `pnpm.cmd`。按 typecheck → lint → build(构建/配置/跨包变更)→ test → 相关 E2E 执行。浏览器、真实 provider、容器和原生交付各自报告实际结果。
75
+ 从当前 package.json 选择实际 scripts,优先 workspace filter;PowerShell 使用 `pnpm.cmd`。按 [分轮验证与集中修复](../hai-app-tests/validation.md) 先收集全部可执行门禁及日志,再归并根因、集中修复、验证失败及受影响项;普通失败不中断独立检查。浏览器、真实 provider、容器和原生交付各自报告实际结果。
76
76
 
77
77
  交付说明需求/范围、带路径与影响的发现、已复用能力和必要缺口、已修正项、已执行门禁/行为证据、同步的 contract/调用方/i18n/配置/文档,以及未验证项和原因。只读审查报告问题与验证建议;不能凭审查清单宣称所有测试通过。
78
78