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

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 (164) hide show
  1. package/README.en.md +40 -4
  2. package/README.ja.md +40 -4
  3. package/README.md +40 -4
  4. package/dist/{src-2V6VP4I5.js → chunk-FI56GONY.js} +32 -3103
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-7DSURTJ4.js → chunk-ZFNBUF77.js} +327 -199
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-6AHNUGBN.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-FC3QRKDO.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-FC3QRKDO.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +920 -336
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/apps/admin/messages/ja-JP.json +38 -0
  21. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  22. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  23. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  24. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  25. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +5 -1
  26. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  27. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  28. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  29. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  30. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  31. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  32. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  33. package/templates/apps/api/package.json.hbs +1 -1
  34. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  35. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +1 -1
  36. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  37. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  38. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +1 -1
  39. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +1 -1
  40. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  41. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  42. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  43. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +1 -1
  44. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  45. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +1 -1
  46. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +19 -8
  47. package/templates/apps/fullstack/package.json.hbs +1 -1
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  49. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  50. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  51. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +5 -2
  52. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  53. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  54. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  55. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  56. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  57. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  58. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  59. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  60. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  61. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  62. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  63. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +9 -4
  64. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  65. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  66. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +5 -5
  67. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  68. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +1 -1
  69. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  70. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +11 -12
  71. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  72. package/templates/apps/h5/messages/ja-JP.json +40 -0
  73. package/templates/apps/h5/src/routes/+layout.svelte.hbs +14 -3
  74. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  75. package/templates/apps/mobile-app/src/App.svelte.hbs +13 -3
  76. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  77. package/templates/apps/website/messages/ja-JP.json +32 -0
  78. package/templates/apps/website/src/routes/+layout.svelte.hbs +10 -2
  79. package/templates/base/e2e/app.spec.ts.hbs +13 -8
  80. package/templates/base/package.json.hbs +4 -1
  81. package/templates/base/playwright.config.ts +15 -4
  82. package/templates/base/src/hooks.server.ts.hbs +15 -6
  83. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  84. package/templates/base/vite.config.ts.hbs +2 -2
  85. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  86. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  87. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  88. package/templates/i18n/project.inlang/settings.json +1 -1
  89. package/templates/shared/e2e-env.ts +98 -0
  90. package/templates/shared/run-e2e.mjs +51 -12
  91. package/templates/skills/AGENTS.md +4 -0
  92. package/templates/skills/bridges/admin/AGENTS.md +5 -1
  93. package/templates/skills/bridges/api/AGENTS.md +2 -0
  94. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +4 -0
  95. package/templates/skills/bridges/h5/AGENTS.md +5 -1
  96. package/templates/skills/bridges/mobile-app/AGENTS.md +5 -1
  97. package/templates/skills/bridges/website/AGENTS.md +5 -1
  98. package/templates/skills/hai-ai/SKILL.md +8 -2
  99. package/templates/skills/hai-ai/guide.md +134 -60
  100. package/templates/skills/hai-ai/reference.md +51 -11
  101. package/templates/skills/hai-api-client/SKILL.md +2 -0
  102. package/templates/skills/hai-api-client/reference.md +17 -5
  103. package/templates/skills/hai-api-contract/SKILL.md +1 -0
  104. package/templates/skills/hai-api-contract/reference.md +4 -2
  105. package/templates/skills/hai-app-create/SKILL.md +4 -0
  106. package/templates/skills/hai-app-create/development.md +20 -4
  107. package/templates/skills/hai-app-create/reference.md +2 -1
  108. package/templates/skills/hai-app-review/SKILL.md +6 -2
  109. package/templates/skills/hai-app-review/differential.md +34 -0
  110. package/templates/skills/hai-app-tests/SKILL.md +3 -1
  111. package/templates/skills/hai-app-tests/reference.md +4 -4
  112. package/templates/skills/hai-app-tests/validation.md +10 -3
  113. package/templates/skills/hai-audit/SKILL.md +10 -8
  114. package/templates/skills/hai-audit/reference.md +3 -0
  115. package/templates/skills/hai-build/reference.md +6 -4
  116. package/templates/skills/hai-cache/SKILL.md +9 -3
  117. package/templates/skills/hai-cache/reference.md +53 -11
  118. package/templates/skills/hai-capacitor/SKILL.md +3 -1
  119. package/templates/skills/hai-capacitor/reference.md +9 -5
  120. package/templates/skills/hai-core/SKILL.md +4 -0
  121. package/templates/skills/hai-core/reference.md +25 -5
  122. package/templates/skills/hai-crypto/SKILL.md +10 -2
  123. package/templates/skills/hai-crypto/reference.md +64 -10
  124. package/templates/skills/hai-datapipe/SKILL.md +2 -0
  125. package/templates/skills/hai-datapipe/reference.md +3 -1
  126. package/templates/skills/hai-deploy/SKILL.md +9 -4
  127. package/templates/skills/hai-deploy/middleware.md +51 -37
  128. package/templates/skills/hai-iam/SKILL.md +24 -8
  129. package/templates/skills/hai-iam/reference.md +181 -154
  130. package/templates/skills/hai-kit/SKILL.md +23 -7
  131. package/templates/skills/hai-kit/reference.md +42 -24
  132. package/templates/skills/hai-payment/SKILL.md +13 -11
  133. package/templates/skills/hai-payment/reference.md +80 -41
  134. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  135. package/templates/skills/hai-reach/SKILL.md +5 -2
  136. package/templates/skills/hai-reach/reference.md +83 -22
  137. package/templates/skills/hai-reldb/SKILL.md +5 -2
  138. package/templates/skills/hai-reldb/reference.md +68 -25
  139. package/templates/skills/hai-scheduler/SKILL.md +8 -5
  140. package/templates/skills/hai-scheduler/reference.md +38 -24
  141. package/templates/skills/hai-serv/SKILL.md +23 -8
  142. package/templates/skills/hai-serv/reference.md +123 -87
  143. package/templates/skills/hai-spec-implement/SKILL.md +4 -0
  144. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  145. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  146. package/templates/skills/hai-spec-implement/templates.md +5 -0
  147. package/templates/skills/hai-spec-implement/workflow.md +2 -0
  148. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  149. package/templates/skills/hai-storage/SKILL.md +8 -2
  150. package/templates/skills/hai-storage/reference.md +64 -25
  151. package/templates/skills/hai-ui/SKILL.md +16 -2
  152. package/templates/skills/hai-ui/guide.md +3 -3
  153. package/templates/skills/hai-ui/reference.md +17 -6
  154. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  155. package/templates/skills/hai-ui-design/patterns.md +26 -0
  156. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  157. package/templates/skills/hai-ui-review/checklist.md +22 -0
  158. package/templates/skills/hai-vecdb/SKILL.md +12 -8
  159. package/templates/skills/hai-vecdb/reference.md +33 -3
  160. package/dist/chunk-7DSURTJ4.js.map +0 -1
  161. package/dist/chunk-AOQ4N657.js +0 -640
  162. package/dist/chunk-AOQ4N657.js.map +0 -1
  163. package/dist/cli-config-templates-6AHNUGBN.js +0 -5
  164. package/dist/src-2V6VP4I5.js.map +0 -1
@@ -18,7 +18,7 @@ description: "在 Android/iOS 应用中接入 @h-ai/capacitor 设备、相机、
18
18
  ## 使用路径
19
19
 
20
20
  - 先判断平台和插件可用性,复用已有 SPA 入口;原生 UI 变化验证权限拒绝、取消和返回路径。
21
- - TokenStorage 在原生端使用安全插件;Web 下不伪造持久化成功。
21
+ - TokenStorage 在原生端使用安全插件;Web/插件不可用或存储失败时拒绝 Promise。调用方处理 setTokens/clear 失败并释放 loading;clear 的实例内屏蔽不替代服务端撤销。
22
22
  - 原生打包前确认公开 API 地址的运行配置入口;fullstack App 使用 `public/runtime-config.js`,构建不会读取部署环境文件。这里只放公开地址,不能放 Token 或密钥。
23
23
 
24
24
  ## 按需参考
@@ -33,3 +33,5 @@ description: "在 Android/iOS 应用中接入 @h-ai/capacitor 设备、相机、
33
33
  - 常见模式
34
34
  - 插件依赖
35
35
  - 相关 Skills
36
+
37
+ 推送仅在实际原生平台调用插件。`push.register({ signal? })` 与 `push.listen(callbacks, { signal? })` 的设置阶段限时 30 秒(包含权限和监听器挂载);失败、超时、取消及 `capacitor.close()` 都会停止回调并移除本次拥有的句柄,迟到的句柄在完成后立即移除,不调用影响其他消费者的 removeAllListeners。listen 成功后信号仍有效,也可调用返回的 cleanup。cleanup 与 close 均返回 `HaiResult<void>`;原生 remove 失败返回 `PUSH_CLEANUP_FAILED`(`capacitor:032`),失败句柄保留供再次调用重试,成功移除的句柄不重复操作。模块关闭后立即不可用;重新初始化前先重试遗留清理。自动化验证控制插件时序,不能代替 Android/iOS 真机 FCM/APNs 权限、注册与反复挂载验收。
@@ -2,7 +2,7 @@
2
2
 
3
3
  从 [SKILL.md](SKILL.md) 选择主题后读取对应小节。代码片段展示 API 用法;省略的业务变量、schema、依赖与 return 上下文需由应用补齐。
4
4
 
5
- > `@h-ai/capacitor` 是 hai-framework 的 Capacitor 原生桥接模块,封装常用原生能力为统一 API,返回 `HaiResult<T>`。与 `@h-ai/api-client` 配合时,Token 仅存于原生安全存储,不回退到 Web `localStorage`。
5
+ > `@h-ai/capacitor` 是 hai-framework 的 Capacitor 原生桥接模块。设备、推送等领域操作返回 `HaiResult<T>`;TokenStorage 是直接 Promise 适配器,失败会拒绝。与 `@h-ai/api-client` 配合时,Token 仅存于原生安全存储,不回退到 Web `localStorage`。
6
6
 
7
7
  ## 运行环境
8
8
 
@@ -60,9 +60,11 @@ await apiClient.init({
60
60
 
61
61
  - Android → Android KeyStore + 加密 SharedPreferences
62
62
  - iOS → Keychain
63
- - Web → **不回退到 localStorage**;`get*()` 返回 `null`,`set*()` / `clear()` 为 no-op
63
+ - Web → **不提供原生存储回退**;适配器方法拒绝 Promise,Web 入口选择 api-client 的 Cookie/内存存储
64
64
 
65
- 所有方法内置 try-catch;原生安全存储异常时 get 返回 `null`、set/clear 静默失败并记录日志。
65
+ 读取、保存与删除失败均拒绝 Promise。调用方通过 `apiClient.auth` 串行更新凭据并捕获 setTokens/clear 失败;界面释放 loading,退出失败仍清理内存身份并提示错误。`clear()` 开始立即屏蔽本实例两类旧凭据,等待两个删除都结束;即使失败也保持屏蔽,相应 key 成功写入后重新开放。屏蔽不等于持久删除,跨实例/重启撤销仍依赖服务端退出/撤销会话。插件恢复后可以在同一实例重试,不缓存永久失败状态。
66
+
67
+ 任一 key 写入失败也会屏蔽整对凭据,避免 access 已落盘而 refresh 保存失败后的半完成登录。登录/注册保存失败后尝试 `auth.clear()` 清理部分数据;清理失败继续显示未确认删除的错误,不能只清界面身份或宣称持久删除成功。
66
68
 
67
69
  #### Preferences 子操作
68
70
 
@@ -167,7 +169,7 @@ await capacitor.statusBar.show()
167
169
  | API | 用途 | 返回值 |
168
170
  | --- | --- | --- |
169
171
  | `capacitor.init()` | 初始化模块(检测环境) | `HaiResult<void>` |
170
- | `capacitor.close()` | 关闭模块,重置状态 | `Promise<void>` |
172
+ | `capacitor.close()` | 关闭模块并回收推送,失败可重试 | `Promise<HaiResult<void>>` |
171
173
  | `capacitor.getPlatform()` | 获取当前平台 | `'android' \| 'ios' \| 'web'` |
172
174
  | `capacitor.isNative()` | 是否为原生环境 | `boolean` |
173
175
  | `capacitor.isInitialized` | 是否已初始化 | `boolean` |
@@ -178,7 +180,7 @@ await capacitor.statusBar.show()
178
180
  | `capacitor.device.getInfo()` | 设备信息 | `HaiResult<DeviceInfo>` |
179
181
  | `capacitor.device.getAppVersion()` | 应用版本 | `HaiResult<{ version, build }>` |
180
182
  | `capacitor.push.register()` | 注册推送 | `HaiResult<PushRegistration>` |
181
- | `capacitor.push.listen(callbacks)` | 监听推送事件 | `HaiResult<() => Promise<void>>`(async 清理函数) |
183
+ | `capacitor.push.listen(callbacks)` | 监听推送事件 | `HaiResult<PushCleanup>`(cleanup 返回 `Promise<HaiResult<void>>`) |
182
184
  | `capacitor.camera.takePhoto(options?)` | 拍照 / 选取图片 | `HaiResult<PhotoResult>` |
183
185
  | `capacitor.statusBar.configure(config)` | 配置状态栏 | `HaiResult<void>` |
184
186
  | `capacitor.statusBar.show()` | 显示状态栏 | `HaiResult<void>` |
@@ -352,3 +354,5 @@ async function captureAndUpload() {
352
354
  - `hai-ui`:移动端 UI 组件(SafeArea、BottomNav 等)
353
355
 
354
356
  原生交付必须单独执行 Tauri/Gradle/Xcode 打包,并在目标 OS/设备验证安装启动、登录/退出、重启会话、安全 TokenStore、真实 API 域名与覆盖升级。Web 构建/E2E 不代替原生验收。保存提交、包 hash、平台/工具链及逐项结果;缺平台写未验证,禁止静默 skip。hai-framework 仓库参考 `.github/workflows/native.yml` 与 desktop-app / mobile-app README 的「原生交付与验收」章节。
357
+
358
+ 推送仅在实际原生平台调用插件。`push.register({ signal? })` 与 `push.listen(callbacks, { signal? })` 的设置阶段限时 30 秒(包含权限和监听器挂载);失败、超时、取消及 `capacitor.close()` 都会停止回调并移除本次拥有的句柄,迟到的句柄在完成后立即移除,不调用影响其他消费者的 removeAllListeners。listen 成功后信号仍有效,也可调用返回的 cleanup。cleanup 与 close 均返回 `HaiResult<void>`;原生 remove 失败返回 `PUSH_CLEANUP_FAILED`(`capacitor:032`),失败句柄保留供再次调用重试,成功移除的句柄不重复操作。模块关闭后立即不可用;重新初始化前先重试遗留清理。自动化验证控制插件时序,不能代替 Android/iOS 真机 FCM/APNs 权限、注册与反复挂载验收。
@@ -18,10 +18,12 @@ description: "配置 hai 应用启动、YAML/环境变量、日志、i18n 与 Ha
18
18
  ## 使用路径
19
19
 
20
20
  - Node 启动用 core.init({ configDir }) 加载配置;勿传 logging 覆盖 _core.yml 中的日志设置,除非任务明确要求。
21
+ - 自动加载和监听精确跳过 `_deploy.yml` / `_deploy.yaml`,部署 CLI 按目标环境和凭据独立解析;业务 `deploy.yml`、手动 `config.load` 不受影响。单项 load 返回失败时记日志并继续,必需配置须 validate 并检查 success;目录扫描、日志设置等异常仍可能传播。
21
22
  - 应用名称、版本、环境、默认语言和日志设置统一从 `_core.yml` 经 core.config 获取,日志使用 core.logger;UI 不另维护硬编码副本。SvelteKit layout / fullstack app.info 仅下发公开字段白名单;独立 Vite mobile-app 在 Node 构建时解析并注入公开默认值,修改这些编译默认值需要重新构建。静态 Web 的公开 API 地址等通过 `runtime-config.js` 覆盖时无需重建镜像,原生包在复制静态资源前准备配置入口。浏览器不加载 YAML,不接收完整模块配置、密钥或日志脱敏上下文。
22
23
  - `HAI_<配置名>_<路径>` 可整体覆盖根/对象/数组;父节点优先,未覆盖时才遍历已有叶子。camelCase 不拆词,约定变量优先于显式插值。
23
24
  - 环境文件采用六类:`.env.example` 完整列出字段用途、默认值、枚举和范围;`.env` 为公共基础;`.env.dev`、`.env.test`、`.env.prod` 为公共环境覆盖,这五类均提交 Git。`.env.local` 为本机私有配置,不提交 Git。优先级为进程环境 > `.env.local` > 环境文件 > `.env`;Vite 在 Node 配置阶段调用 `core.config.loadEnvFiles({ mode, envDir })` 并检查 success 后注入进程,Node 脚本按相同顺序传入 `--env-file`。`core.init` 不自行读取 dotenv。
24
25
  - 配置 getter 与 ID/logger 方法不是 HaiResult;validate/load 等须检查 success。
26
+ - 日志可用 `{ error }` 记录原生 Error;两端先规范化诊断再字段/URL 脱敏,配置 redact 路径仍生效。原异常不变,错误子图循环/深度用固定标记;message/stack 自由文本中的秘密需调用方先清理或遮蔽整条路径。
25
27
  - 初始化前创建的默认、模块与嵌套 child Logger 在后续输出中应用当前全局 logging;configure/setLevel 也作用于已有实例。create 的显式 level/format/context 优先,未指定项动态继承。浏览器 json/pretty 与附加 redact 路径实际生效,消息文本仍由调用方脱敏。
26
28
 
27
29
  ## 按需参考
@@ -36,3 +38,5 @@ description: "配置 hai 应用启动、YAML/环境变量、日志、i18n 与 Ha
36
38
  - 常见模式
37
39
  - 相关 Skills
38
40
  - 浏览器侧使用
41
+
42
+ Node 请求/SSR 必须用 `core.i18n.runWithLocale(locale, callback)`(kit 同名方法)包裹实际异步 resolve;serv 自动建立请求语言上下文。浏览器 setGlobalLocale/createLocaleStore 继续管理应用语言,SSR 的 UI setter/store 不写全局值。显式 MessageOptions.locale 优先;进程默认语言仅在启动时设置。
@@ -34,13 +34,15 @@
34
34
  ```typescript
35
35
  import { core } from '@h-ai/core'
36
36
 
37
- // 自动加载 config/ 目录下的所有 _*.yml 和 *.yml
37
+ // 自动加载运行时 .yml / .yaml,跳过 _deploy.yml / _deploy.yaml
38
38
  core.init({ configDir: './config' })
39
39
 
40
40
  // 也可指定日志级别和配置监听
41
41
  core.init({ configDir: './config', watchConfig: true })
42
42
  ```
43
43
 
44
+ `_deploy.yml` / `_deploy.yaml` 由部署 CLI 在加载目标环境与凭据后独立解析,不参加 Core 的自动加载和监听;业务 `deploy.yml`、手动 `core.config.load` 不受影响。单项 `config.load` 返回失败时记录错误并继续,应用须 validate 必需配置并检查 success。目录扫描、日志设置等异常仍可能传播,不能把同步 void 当作所有配置加载成功的证明。
45
+
44
46
  ### 2. 使用功能
45
47
 
46
48
  ```typescript
@@ -60,7 +62,7 @@ const formErrors = core.zodValidation.mapZodErrorToFormErrors(zodError, getMessa
60
62
 
61
63
  | 方法 | 签名 | 说明 |
62
64
  | ----------- | --------------------------------- | ----------------------------------------- |
63
- | `core.init` | `(options?: CoreOptions) => void` | 初始化(同步,加载配置目录中的所有 YAML) |
65
+ | `core.init` | `(options?: CoreOptions) => void` | 同步加载运行时 YAML,排除两个 `_deploy` 保留文件 |
64
66
 
65
67
  > `CoreOptions`:`{ logging?: Partial<LoggingConfig>, configDir?: string, watchConfig?: boolean }`
66
68
  >
@@ -150,6 +152,8 @@ core.logger.error('Failed to connect', { error })
150
152
  core.logger.debug('Processing item', { id, step: 'validation' })
151
153
  ```
152
154
 
155
+ 原生 `Error` 的 `name/message/stack/cause`、`AggregateError.errors` 与可枚举自有上下文先转成副本,再走字段/URL 脱敏与附加 `redact`;两端原异常均不变。Error 子图循环用 `[Circular]`,达到 32 层用 `[MaxDepth]`,自定义替换值也经过同一规范化;普通对象的循环与替换语义不变。自由文本不会自动解析秘密,可配置 `redact: ['error.message', 'error.stack']` 遮蔽整条诊断路径。
156
+
153
157
  **管理方法**:
154
158
 
155
159
  | 方法 | 签名 | 说明 |
@@ -195,18 +199,21 @@ const level = core.logger.getLevel() // 'debug'
195
199
  | 方法 | 签名 | 说明 |
196
200
  | --------------------- | --------------------------------------------------------------------- | -------------- |
197
201
  | `getGlobalLocale` | `() => string` | 获取当前语言 |
198
- | `setGlobalLocale` | `(locale: string) => void` | 设置全局语言 |
202
+ | `setGlobalLocale` | `(locale: string) => void` | 浏览器语言/启动默认值;请求内只改当前上下文 |
203
+ | `runWithLocale` | `<T>(locale: string, callback: () => T) => T` | Node 请求/SSR 异步语言隔离 |
199
204
  | `createMessageGetter` | `(messages: Record<string, Record<string, string>>) => MessageGetter` | 创建消息获取器 |
200
205
 
201
206
  创建模块 i18n:
202
207
 
203
208
  ```typescript
204
209
  import enUS from '../messages/en-US.json'
210
+ import jaJP from '../messages/ja-JP.json'
205
211
  import zhCN from '../messages/zh-CN.json'
206
212
 
207
213
  const m = core.i18n.createMessageGetter({
208
214
  'zh-CN': zhCN,
209
215
  'en-US': enUS,
216
+ 'ja-JP': jaJP,
210
217
  })
211
218
 
212
219
  // 使用
@@ -301,7 +308,7 @@ core.id.isValidNanoId('abc123', 6) // true/false
301
308
 
302
309
  - 生成 UUID **一律用 `core.id.uuid()`**,禁止裸调 `crypto.randomUUID()`。
303
310
  - 原因:`crypto.randomUUID` 仅在**安全上下文(HTTPS / localhost)**下可用,生产 http 环境会抛
304
- `crypto.randomUUID is not a function`;`core.id.uuid()` 已做 `randomUUID → getRandomValues → Math.random` 三级兜底,Node.js 与浏览器通用。
311
+ `crypto.randomUUID is not a function`;`core.id.uuid()` 仅使用 randomUUID 或密码学安全的 getRandomValues;两者不可用时明确抛错,绝不降级到 Math.random。
305
312
  - 需要不带横线的 compact 形式:`core.id.uuid().replaceAll('-', '')`。
306
313
  - 代码评审与新建代码时以此为准,发现裸调 `crypto.randomUUID()` 一律改走 `core.id.uuid()`。
307
314
 
@@ -328,7 +335,7 @@ core.object.omit(obj, ['c', 'd'])
328
335
  ```typescript
329
336
  core.string.capitalize('hello') // 'Hello'
330
337
  core.string.kebabCase('helloWorld') // 'hello-world'
331
- core.string.constantTimeEqual(a, b) // 防时序攻击比较
338
+ core.string.constantTimeEqual(a, b) // Node 使用原生等长比较;浏览器不保证恒时
332
339
  ```
333
340
 
334
341
  #### 数组操作 — `core.array`
@@ -533,3 +540,16 @@ logger.debug('Data fetched', { count: items.length })
533
540
  | context 输出 | pretty 追加 JSON 上下文,json 合并上下文到结构化 JSON 字符串 |
534
541
 
535
542
  云资源输出格式与部署配置消费见 [hai-deploy](../hai-deploy/SKILL.md);这里的父节点覆盖规则同样适用。
543
+
544
+ ### URL 日志脱敏
545
+
546
+ 使用 `core.sanitize.sanitizeUrl(url)` 统一处理连接地址;它会遮蔽用户名、密码、敏感 query 参数和 OAuth 键值 fragment。非法地址返回固定占位符。`sanitizeSensitiveFields` 对 URL 类字段使用同一规则;自定义 matcher 同样匹配参数名,不能只遮蔽 userinfo 后记录含 token 的 URL。
547
+
548
+ ```ts
549
+ const safeUrl = core.sanitize.sanitizeUrl("https://example.com/a?token=value&limit=10")
550
+ // https://example.com/a?token=[REDACTED]&limit=10
551
+ ```
552
+
553
+ Node 请求/SSR 必须用 `core.i18n.runWithLocale(locale, callback)`(kit 同名方法)包裹实际异步 resolve;serv 自动建立请求语言上下文。浏览器 setGlobalLocale/createLocaleStore 继续管理应用语言,SSR 的 UI setter/store 不写全局值。显式 MessageOptions.locale 优先;进程默认语言仅在启动时设置。
554
+
555
+ Node 入口的 core.string.constantTimeEqual 将原始 UTF-16 单元编码后使用 node:crypto.timingSafeEqual 比较等长字节,长度不相同时直接 false,长度本身不受保护。浏览器实现仅避免按内容提前返回,不提供 JavaScript 引擎级恒时保证,不用于服务端凭证校验。外围分支、编码和请求处理不因此自动成为恒时。
@@ -5,6 +5,8 @@ description: "实现 SM2/SM3/SM4 加解密、验签、密码哈希,或通过 s
5
5
 
6
6
  # hai-crypto
7
7
 
8
+ transport 仅支持 v2:SM2 发送方签名、独立密钥的 Encrypt-then-HMAC、HTTP 上下文/响应状态绑定及共享原子防重放。GET/HEAD、空 POST 与空响应也认证;无降级开关。TLS 信任边界、5 分钟窗口、nonce 持久化与实现示例见 [参考](reference.md)。
9
+
8
10
  ## 能力契约
9
11
 
10
12
  | 项目 | 契约 |
@@ -13,13 +15,19 @@ description: "实现 SM2/SM3/SM4 加解密、验签、密码哈希,或通过 s
13
15
  | 适用场景 | 应用加密、验签、密码保存或传输保护 |
14
16
  | 输入 | 无参 init、字节/文本、密钥和 transport 装配选项 |
15
17
  | 输出 | 加解密/验签的 HaiResult;transport 工厂按公开类型使用 |
16
- | 限制 | 先 init;password 仅服务端,浏览器仅支持部分能力。传输通过 serv/kit/api-client 顶层装配,不导入内部工厂。 |
18
+ | 限制 | crypto 操作先 init;readTransportBody/parseTransportServerKeyRing 为无需 init 的纯工具。password 仅服务端,浏览器仅支持部分能力。传输通过 serv/kit/api-client 顶层装配,不导入内部工厂。 |
17
19
 
18
20
  ## 使用路径
19
21
 
20
22
  1. `crypto.init()` 无配置参数,检查结果;确认目标端是否支持所需能力,password 仅 Node。
21
- 2. 选用公开 asymmetric/hash/symmetric/password 操作,检查 HaiResult;SM3 HMAC 不等同于 SHA-256 HMAC。
23
+ 2. 选用公开 asymmetric/hash/symmetric/password 操作,检查 HaiResult;SM3 HMAC 不等同于 SHA-256 HMAC。SM4 使用 `symmetric.generateKey()` 生成随机密钥,不提供密码 KDF;密码存储使用服务端 `password.hash()`。
22
24
  3. 传输加密经 serv/kit/api-client 顶层配置接入,统一协商与刷新路径;不导入内部工厂。
25
+
26
+ 握手公钥先用无需 init 的 `isValidSM2PublicKey` 验证编码和 SM2 曲线点;内置 manager/registry 也校验,serv/kit 在写存储前拒绝。DB registry 用事务共享容量(maxClients 默认 10000、各节点一致),满容量拒绝、不驱逐有效 nonce;配置有限 ttlSeconds,并由应用周期调用 DB 工厂返回的 `pruneExpired()`(每次公钥/nonce 各最多 100 条)。Redis 使用原生 TTL,容量由部署管理。TTL 显式值只能为正整数秒,详见参考中的范围和迁移边界。
27
+
28
+ 服务端必须显式注入受管 `serverKeyRing` 与共享 `keyStore`,没有进程随机私钥或内存 registry 默认值。请求携带协商 serverKeyId;各节点同 ring + registry 支持跨节点、重启和新旧版本轮换。密钥只从 Secret/私有环境注入,不进入公开配置、客户端或日志。开发/测试的内存 registry 须显式选择。
29
+
30
+ 传输字节预算统一使用根入口 `TRANSPORT_BODY_LIMITS`:握手 JSON 16 KiB、公钥 4 KiB、明文 1 MiB、密文 JSON 2 MiB + 32 KiB。自定义适配器用纯函数 `readTransportBody` 消费原流并有界读取;不要 clone/tee。客户端握手超时 30 秒;取消和超限释放 reader,异常密文响应 reject。
23
31
  4. 验证加解密往返、错误密钥/签名和关闭,密钥与明文不写日志或浏览器公开配置。
24
32
 
25
33
  ## 按需参考
@@ -2,6 +2,11 @@
2
2
 
3
3
  > `@h-ai/crypto` 提供非对称加密、哈希、对称加密与密码哈希能力,支持 Node.js 与浏览器双端。
4
4
 
5
+
6
+ 认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求有效期 5 分钟,未来时钟偏差最多 30 秒;共享 registry 原子消费 (clientId, requestId),nonce 至少保留到 issuedAt + 6 分钟以覆盖节点偏差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
7
+
8
+ 空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口只能经 excludePaths 显式排除。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
9
+
5
10
  ## 运行环境
6
11
 
7
12
  **Node.js + 浏览器双端可用。** 浏览器端通过 `crypto.init()` 初始化(无需配置参数)。
@@ -48,7 +53,7 @@ await crypto.close()
48
53
  | `decrypt` | `(ciphertext, privateKey, options?) => HaiResult<string>` | 私钥解密 |
49
54
  | `sign` | `(data, privateKey, options?) => HaiResult<string>` | 私钥签名 |
50
55
  | `verify` | `(data, signature, publicKey, options?) => HaiResult<boolean>` | 公钥验签 |
51
- | `isValidPublicKey` | `(key: string) => boolean` | 校验公钥格式 |
56
+ | `isValidPublicKey` | `(key: string) => boolean` | 校验公钥编码和 SM2 曲线点 |
52
57
  | `isValidPrivateKey` | `(key: string) => boolean` | 校验私钥格式 |
53
58
 
54
59
  ```typescript
@@ -97,10 +102,11 @@ const valid = crypto.hash.verify('hello', hash.data!)
97
102
  | `decrypt` | `(payload, key) => HaiResult<string>` | 解密结构化密文 |
98
103
  | `encryptWithIV` | `(data, key) => HaiResult<EncryptWithIVResult>` | CBC 模式加密(自动生成 IV) |
99
104
  | `decryptWithIV` | `(ciphertext, key, iv) => HaiResult<string>` | CBC 模式解密 |
100
- | `deriveKey` | `(password, salt) => string` | 从密码和盐值派生密钥 |
101
105
  | `isValidKey` | `(key: string) => boolean` | 校验密钥格式 |
102
106
  | `isValidIV` | `(iv: string) => boolean` | 校验 IV 格式 |
103
107
 
108
+ SM4 密钥使用 `generateKey()` 生成;本模块不提供密码 KDF。密码存储使用服务端 `crypto.password.hash()`,不要将密码或密码哈希直接用作 SM4 密钥。
109
+
104
110
  ```typescript
105
111
  const key = crypto.symmetric.generateKey()
106
112
 
@@ -145,7 +151,7 @@ if (hashed.success) {
145
151
 
146
152
  | 方法 | 签名 | 说明 |
147
153
  | --- | --- | --- |
148
- | `createServer` | `(options?) => HaiResult<TransportEncryptionManager>` | 创建服务端传输加密管理器 |
154
+ | `createServer` | `({ serverKeyRing, keyStore }) => HaiResult<TransportEncryptionManager>` | 校验受管密钥环并创建管理器,两项必填 |
149
155
  | `createClient` | `({ keyExchangeUrl, fetch? }) => TransportClient` | 创建客户端 encryptedFetch 会话 |
150
156
  | `protocol` | `TRANSPORT_PROTOCOL` | 协议常量:`X-Client-Id`、`X-Encrypted`、默认协商路径 |
151
157
 
@@ -155,10 +161,20 @@ if (hashed.success) {
155
161
  | --- | --- | --- |
156
162
  | `createInMemoryKeyStore` | `(maxClients?) => TransportKeyStore` | 默认 FIFO 内存实现 |
157
163
  | `createRedisTransportKeyStore` | `({ cache, ttlSeconds? }) => TransportKeyStore` | 基于 `@h-ai/cache` 的共享实现,通常配 Redis |
158
- | `createReldbTransportKeyStore` | `({ reldb, ttlSeconds? }) => TransportKeyStore` | 基于 `@h-ai/reldb` 的共享实现,自动建表 |
164
+ | `createReldbTransportKeyStore` | `({ reldb, ttlSeconds?, maxClients? }) => ReldbTransportKeyStore` | 自动建表、共享事务容量和有界维护 |
165
+
166
+ 所有 registry 注册前验证非压缩 SM2 曲线点(128 hex 或 04 + 128 hex);纯函数 `isValidSM2PublicKey(value)` 无需 init,serv/kit 在写存储前以 400 拒绝畸形输入。Redis/DB TTL 省略则不过期,显式值须为 1–2147483647 整数秒,建议有限 TTL。Redis 用原生逐键 TTL,容量由部署管理;noeviction 防止提前驱逐 nonce。DB `maxClients` 默认 10000,正安全整数且各节点须与持久容量行一致;事务锁容量行后计数和插入,容量满拒绝、不驱逐有效公钥或 nonce。
167
+
168
+ DB 首次建表失败可重试;公钥过期使用 DB epoch 毫秒,DB 与节点均须同步时钟。注册自动执行一批清理,应用定期调用 `await registry.pruneExpired()` 处理低流量维护:每次最多 100 条过期公钥和 100 条过期 nonce,不建隐式计时器。旧 timestamp 表须停机迁移到 created_at_ms/expires_at_ms BIGINT;容量更改需同步持久记录和所有节点,不能清空有效 nonce。
169
+
170
+ ```ts
171
+ const registry = createReldbTransportKeyStore({ reldb, ttlSeconds: 3600, maxClients: 10000 })
172
+ // 由应用自己的维护调度器调用;失败后可重试。
173
+ await registry.pruneExpired()
174
+ ```
159
175
 
160
176
  ```typescript
161
- const server = crypto.transport.createServer({ maxClients: 10000 })
177
+ const server = crypto.transport.createServer({ serverKeyRing, keyStore })
162
178
  if (!server.success)
163
179
  return server
164
180
 
@@ -179,23 +195,57 @@ import { cache } from '@h-ai/cache'
179
195
  await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
180
196
 
181
197
  const sharedServer = crypto.transport.createServer({
198
+ serverKeyRing,
182
199
  keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
183
200
  })
184
201
  ```
185
202
 
186
203
  使用流程:
187
204
  1. 先 `await crypto.init()`。
188
- 2. 服务端 `createServer()` 后暴露一个 POST 密钥协商端点:接收 `clientPublicKey`,返回 `serverPublicKey + clientId`。
205
+ 2. 服务端显式传入受管 serverKeyRing 和共享 keyStore,协商返回 `serverKeyId + serverPublicKey + clientId`。
189
206
  3. 客户端 `createClient({ keyExchangeUrl })` 后,首次 `init()` / `encryptedFetch()` 会自动做密钥协商。
190
- 4. 协商完成后,请求附带 `X-Client-Id`;若请求有 body,则自动包装成 `{ encryptedKey, ciphertext, iv }`。
191
- 5. 服务端先 `decryptRequest()`,业务处理后再 `encryptResponse(clientId, data)`。
207
+ 4. 所有请求均发送 v2 认证封装;包括 GET/HEAD 和无 input POST,不能只附带 clientId。
208
+ 5. 自定义适配器先 await authenticateTransportRequest(manager, request),再用 manager.encryptResponse(verified.data.context, data, status) 关联响应。
192
209
  6. 客户端收到 `X-Encrypted: true` 的响应后自动解密;`destroy()` 可清空当前会话。
193
210
 
194
211
  `encryptedFetch()` 与原生 fetch 一样会 reject:网络错误、密钥协商失败、请求加密失败或响应解密失败都应在调用方按 fetch 错误处理策略捕获。
195
212
 
213
+ ### 受管服务端密钥与轮换
214
+
215
+ `serverKeyRing` 为 `{ activeKeyId, keys: { [id]: { publicKey, privateKey } } }`,ID 为 1–64 个字母、数字、下划线或连字符。根入口 `parseTransportServerKeyRing(secret)` 接受服务端 Secret JSON 或配置对象,返回 HaiResult,错误不附带原始 Secret 或解析片段。工厂验证每对公私钥并捕获快照;没有隐式随机私钥或 memory registry 回退。`keyStore` 也必填,生产用共享 Redis/数据库;开发/测试可显式 `createInMemoryKeyStore(capacity)`。
216
+
217
+ 示例服务端读取 `process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING`,检查解析结果后仅传给服务端工厂;禁止写入普通 YAML、PUBLIC_/VITE_、日志或响应。CLI 仅在创建服务端项目时向忽略的 `.env.local` 生成一次本机开发 ring,生产由 Secret Manager 分发同一受管 ring。
218
+
219
+ 请求密文 `EncryptedRequestPayload` 携带协商的 `serverKeyId`;服务端只按 ID 选择私钥,未知/撤销 ID 拒绝,不尝试其它 key。轮换先向所有节点分发包含新旧版本的 ring,再切 activeKeyId 并滚动重建 manager;过渡期后删除旧 ID 并更新所有节点。旧客户端 destroy 后重协商;禁止自动重放非幂等请求。共享 ring + registry 支持跨节点与重启,不依赖 sticky session。
220
+
221
+ ```ts
222
+ import { parseTransportServerKeyRing } from '@h-ai/crypto'
223
+
224
+ const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
225
+ if (!ring.success)
226
+ throw new Error(ring.error.message)
227
+ const manager = crypto.transport.createServer({ serverKeyRing: ring.data, keyStore: sharedRegistry })
228
+ ```
229
+
230
+ ### 传输正文预算与自定义适配器
231
+
232
+ serv、kit 和客户端统一从根入口导入 `TRANSPORT_BODY_LIMITS`:握手 JSON 16 KiB、单个公钥 4 KiB、明文 1 MiB、密文 JSON 2 MiB + 32 KiB,均为实际 UTF-8 字节上限,不使用 Content-Length 判定。客户端共享握手最多等待 30 秒;单个业务请求取消只中断自己的等待,其 signal 继续用于请求及响应读取。异常加密响应会 reject,不返回原密文。
233
+
234
+ 根入口纯函数 `readTransportBody(stream, { maxBytes, signal? })` 无需 init,直接消费原始流;超限抛 RangeError,无效 UTF-8 抛 TypeError,保留读取异常或 signal.reason。超限/取消会立即取消流并释放 reader,清理失败不覆盖原异常。不要把 clone/tee 分支交给它,否则另一分支仍可能缓冲正文。成功读取后重建 Request/Response,清除旧 Content-Length/Content-Encoding 并保留 signal。
235
+
236
+ ```ts
237
+ import { readTransportBody, TRANSPORT_BODY_LIMITS } from '@h-ai/crypto'
238
+
239
+ const text = await readTransportBody(request.body, {
240
+ maxBytes: TRANSPORT_BODY_LIMITS.keyExchangeBytes,
241
+ signal: request.signal,
242
+ })
243
+ const payload: unknown = JSON.parse(text)
244
+ ```
245
+
196
246
  常规应用优先使用上层封装:
197
- - serv:`serv.createApp({ transport: { crypto } })`
198
- - kit:`kit.createHandle({ crypto: { crypto, transport: true } })` + `kit.client.create({ transport: { crypto } })`
247
+ - serv:`serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })`
248
+ - kit:`kit.createHandle({ crypto: { crypto, transport: { serverKeyRing, keyStore } } })` + `kit.client.create({ transport: { crypto } })`
199
249
  - api-client:`apiClient.init({ transport: { crypto } })`
200
250
 
201
251
  ## 错误码 — `HaiCryptoError`
@@ -254,3 +304,7 @@ if (verifyResult.success && verifyResult.data) {
254
304
  - `hai-serv` / `hai-api-client`:跨域 HTTP API 的透明传输加密
255
305
 
256
306
  管理端在线加密演示直接调用 Crypto SM3/SM4 API,SM4 使用随机临时密钥和 IV,并在显示成功前解密校验;密钥不保存。输入改变即清空旧结果,禁止用随机摘要或 Base64 伪装算法结果。
307
+
308
+ ## 生产共享状态
309
+
310
+ 生产传输 registry 必须声明 stateScope=shared 并实际使用 Redis 或 PostgreSQL/MySQL。memory、包装 memory 的 Redis 工厂、包装 SQLite 的 RelDB 工厂会被拒绝;各节点加载相同受管版本化密钥环。 NODE_ENV 或 HAI_CORE_ENV 为 production 时在初始化成功前检查,只有 HAI_E2E=1/HAI_E2E_MOCK=1 明确测试豁免。不要将租约当作最终写入条件,外部未知效果须按稳定业务键核对。
@@ -33,3 +33,5 @@ description: "使用 @h-ai/datapipe 清洗和分块文本、组合预处理管
33
33
  - [错误码 — `HaiDatapipeError`](reference.md#错误码--haidatapipeerror)
34
34
  - [常见模式](reference.md#常见模式)
35
35
  - [相关 Skills](reference.md#相关-skills)
36
+
37
+ 分块 `maxSize` 默认 1500,`overlap` 默认 0 且必须满足 `0 <= overlap < maxSize`。word 模式两者均按非空白词计量(连续中文可视为一个长词),其余模式均按 Unicode code point 计量,不是 UTF-16 length、字节或模型 token。上限包含重叠与分隔符;自然句/段/页/自定义边界只影响优先切点,超长段统一按 `maxSize-overlap` 滑窗。保留原始空白、标点、分页符及自定义分隔符,移除重叠后可还原全文;不再 split/join 或 trim 分块。Markdown section 之间不重叠,section 内使用相同硬上限,代码围栏内的标题保留为正文;仅 `markdownKeepTitle=false` 明确移除标题行。空/全空白的整体输入返回空数组。自定义正则须由可信配置提供,非法表达式返回 CHUNK_FAILED。
@@ -73,7 +73,7 @@ datapipe.chunk(text: string, options: ChunkOptionsInput): HaiResult<DataChunk[]>
73
73
  | 字段 | 类型 | 默认 | 说明 |
74
74
  | ------------------ | --------- | ---- | ------------------------------ |
75
75
  | `mode` | `ChunkMode` | — | 分块模式(必填) |
76
- | `maxSize` | `number` | 1000 | 分块最大大小 |
76
+ | `maxSize` | `number` | 1500 | 分块最大大小 |
77
77
  | `overlap` | `number` | 0 | 重叠大小(上下文衔接) |
78
78
  | `separator` | `string` | — | 自定义分隔符正则(mode=custom)|
79
79
  | `markdownMinLevel` | `number` | 2 | Markdown 最低标题级别(1-6) |
@@ -200,3 +200,5 @@ if (!result.success) {
200
200
  - `hai-vecdb`:向量数据库存储(先生成 embedding,再入库)
201
201
  - `hai-ai`:LLM 与 Embedding 能力
202
202
  - `hai-core`:HaiResult 模型
203
+
204
+ 分块 `maxSize` 默认 1500,`overlap` 默认 0 且必须满足 `0 <= overlap < maxSize`。word 模式两者均按非空白词计量(连续中文可视为一个长词),其余模式均按 Unicode code point 计量,不是 UTF-16 length、字节或模型 token。上限包含重叠与分隔符;自然句/段/页/自定义边界只影响优先切点,超长段统一按 `maxSize-overlap` 滑窗。保留原始空白、标点、分页符及自定义分隔符,移除重叠后可还原全文;不再 split/join 或 trim 分块。Markdown section 之间不重叠,section 内使用相同硬上限,代码围栏内的标题保留为正文;仅 `markdownKeepTitle=false` 明确移除标题行。空/全空白的整体输入返回空数组。自定义正则须由可信配置提供,非法表达式返回 CHUNK_FAILED。
@@ -18,12 +18,13 @@ description: "构建并发布或交付已有镜像:完整离线包、按服务
18
18
  ## 操作流程
19
19
 
20
20
  1. 读取项目 scripts、images/ 中的 Dockerfile、hai.build.json 与服务目录配置。创建默认带开发期部署工具;用户明确 --no-deploy 时不生成部署入口。构建细节见 [hai-build](../hai-build/SKILL.md)。
21
- 2. 选择环境,验证 .env.<mode> 存在,再处理 SSH 与必要中间件。使用人类可读字段说明,复用有效配置;私有参数写 .env.local,私钥仅引用路径。环境规则见 [middleware.md](middleware.md)。
21
+ 2. 选择环境,验证 .env.<mode> 存在,再处理部署目标与必要中间件。直接部署缺少 SSH 参数时按人类可读字段说明补齐,已有完整配置直接复用;--bundle-only 不询问 SSH。私有参数写 .env.local,私钥仅引用路径。环境规则见 [middleware.md](middleware.md)。
22
22
  3. 先校验所选制品、版本、服务和 SHA-256。--build 只构建所选应用镜像;不带它时缺产物明确失败,不隐式补构建。格式版本 3 记录 appVersion、buildId、partial、hasWeb、镜像归档及显式配置的 appContainerPort/webContainerPort;部分构建也保留未选镜像端口,缺省使用部署默认值。同一应用版本可重建,镜像引用区分实际内容;禁止 latest 与无版本镜像。
23
23
  4. 完整包自动导出实际自建中间件镜像并去重;只更新 Web 不重新打包 API/中间件。打包机需容器引擎导出中间件,服务器仅 load,禁止 pull/build。外部连接及云开通不能伪称为离线自包含服务。
24
24
  5. --services 使用目标服务器最后成功状态为基线,检查端口/拓扑及未选依赖,保留未选配置、凭据、网络和卷。脚本先按项目/服务标签仅删除选中旧容器(保留卷),再 up -d --no-deps --no-build --pull never --no-recreate 创建新容器并保留其他容器,避免 Compose Provider 连带重建反向依赖,禁止整个项目 down。storage-init 是一次性创建 S3 桶,涉及存储初始化才运行。仅更新 storage 也需含 app 镜像的制品运行初始化;最近为 web-only 时,用 --artifact-dir 选择原完整制品,或先 pnpm build --target docker --images app,不会更新运行中的 app 服务。
25
- 6. --bundle-only 不连接 SSH,同时输出 tar 和 -deploy/ 操作目录。Windows deploy.ps1、Linux/macOS deploy.sh 上传并执行包内 apply.sh;完全隔离时搬运 tar,独立目录解包执行 sh apply.sh,可传部署根目录。脚本不依赖源码、Node 或 pnpm。
25
+ 6. 部署缓存、tar 和 -deploy/ 操作目录默认位于工程根 .hai/deploy/<环境>/,与 .hai/build/ 使用同一工程根;--output 显式覆盖 tar 路径,相对命令执行目录解析。--bundle-only 不连接 SSH,生成带逐项说明的 deploy-target.json。执行上传脚本前先按说明修改并核对该 JSON,Windows deploy.ps1、Linux/macOS deploy.sh 均读取此文件,上传并执行包内 apply.sh。完全隔离时搬运 tar,独立目录解包执行 sh apply.sh,可传部署根目录。脚本不依赖源码、Node 或 pnpm;本机运行 deploy.sh 需 sh、awk、ssh、scp。
26
26
  7. 工具预检、基线、上传、校验、镜像导入、服务更新和 HTTP 检查分别报告。失败返回非零退出码,结果文件保留失败阶段;只有检查成功才更新服务器 current。已有部署失败尝试恢复所选服务的旧容器和配置;数据卷保留,已写业务数据不回退,恢复后仍需验证健康。禁止把本地打包记录当服务器成功状态。
27
+ 8. 部署及恢复全程用 Linux flock 持有固定 `.deploy.lock`;不得删除/替换文件或按时间抢锁。父进程强杀后,继承 FD 的活跃子命令仍持锁;所有持有进程退出或重启后自动释放。旧 `.deploy-lock` 目录保持原样,升级前结束旧版部署进程。强杀/断电不执行恢复 trap,缺结果文件须人工核对容器、镜像和 current 后用新包恢复,锁释放不代表副作用已回退。
27
28
 
28
29
  ## CLI
29
30
 
@@ -31,7 +32,7 @@ description: "构建并发布或交付已有镜像:完整离线包、按服务
31
32
  pnpm run deploy --env prod --build
32
33
  pnpm build --target docker
33
34
  pnpm run deploy --env prod --artifact-dir .hai/build/docker/<buildId>
34
- pnpm run deploy --env prod --bundle-only --output release-prod.tar
35
+ pnpm run deploy --env prod --bundle-only
35
36
  pnpm build --target docker --images web
36
37
  pnpm run deploy --env prod --services web
37
38
  ```
@@ -42,8 +43,12 @@ pnpm run deploy --env prod --services web
42
43
 
43
44
  deploy.init 后检查 HaiResult.success,deploy.deployApp 接收 artifactDir、bundleOnly、selectedServices、runtime 与 onProgress;finally 中 deploy.close。deploy.scan 和 deploy.credentials.* 不依赖 init。Vercel 只消费已有 .vercel/output,不执行源码构建,也不支持 Compose 离线/部分更新。
44
45
 
46
+ 内部命令工具须显式枚举 `secrets`(包括转换后的敏感字面量),成功/失败/回调均跨块脱敏;不得向日志附加原始异常 cause。捕获默认每流末尾 1 Mi 字符,结构化消费先检查截断标志;异步输出回调按序等待并保持 pipe 背压。进度每行最多解析 512 字符,超时后确认远程执行状态。详细边界见 [middleware.md](middleware.md#命令输出)。
47
+
48
+ 超时或输入/输出失败会终止本地受管理 Job/进程组并等待核验;Windows 用系统 PowerShell/.NET 内嵌 Job,先挂起创建、分配成功才恢复,失败不降级裸进程;POSIX 后代须保留继承的进程组。`terminationTimeoutMs` 默认 10000、最大 60000 毫秒,核验失败返回 unknown。失败分支用 `getDeployCommandState(error)` 读取 localTermination 和 remoteExecution;CLI 分别显示。本地已退出不能证明 SSH 远端停止;仅新连接读到完整、ID 匹配的原子结果才确认 finished,给出结果路径/退出码。未知状态不自动重跑发布或回滚,先核对 current、步骤和实际服务。
49
+
45
50
  ## 离线交付与验证
46
51
 
47
- Linux 预备 Docker/Podman、支持 --pull never 的 Compose(Docker Compose v2 或 podman-compose 1.6.0 及以上)、sh、tar、sha256sum、awk、curl 及目录/容器权限。完整 tar 包含全部自建中间件归档;目标架构须匹配。包中有私有配置,安全搬运,不提交 Git。离线构建额外准备基础镜像、Linux 依赖缓存、工具链与原生预编译包;Windows node_modules 不能替代。
52
+ Linux 预备 Docker/Podman、支持 --pull never 的 Compose(Docker Compose v2 或 podman-compose 1.6.0 及以上)、sh、tar、sha256sum、awk、curl、util-linux flock 及目录/容器权限,部署目录文件系统须支持 flock。完整 tar 包含全部自建中间件归档;目标架构须匹配。包中有私有配置,安全搬运,不提交 Git。离线构建额外准备基础镜像、Linux 依赖缓存、工具链与原生预编译包;Windows node_modules 不能替代。
48
53
 
49
54
  按 [分轮验证与集中修复](../hai-app-tests/validation.md) 收集适用的 typecheck、lint、build、test、scaffold gates 和 test:deploy-gates,汇总后集中修复、再验证。实际验证无 registry 网络的首次部署、登录刷新、同版本重发、只更新 Web/API、损坏归档、缺基线、健康失败与恢复。Windows/Linux/macOS 上传入口分别执行验收;原生安装包在对应 OS 验证。测试临时工程、容器、镜像、数据和进程在 finally/teardown 清理,只清理测试拥有的资源;日志、截图和报告保留到分析和交付结束再清理。
@@ -4,79 +4,93 @@
4
4
 
5
5
  `hai deploy` 首先输入部署环境,或用 `hai deploy --env test` 指定。`.env.test` / `.env.prod` 必须由用户先创建;缺失时停止,不代替用户创建一个空文件继续部署。指定要部署的目录,例如 `hai deploy packages/my-app-serv --env test`,该目录必须对应 lockfile 的一个 importer;不遍历 apps/packages 或猜测 fullstack 布局。环境文件默认取当前目录(有 `.env` 时)或 lockfile 根目录,必要时传 `--env-dir`。
6
6
 
7
- 环境文件遵循项目的六文件规范:`.env.example` 为带说明/枚举/范围的完整清单,`.env` 为公共基础,`.env.dev/test/prod` 为公共环境覆盖,以上五类提交 Git;`.env.local` 为忽略 Git 的本机私有覆盖。加载顺序为进程 > `.env.local` > 目标环境 > `.env`。真实密码、API Key、含认证信息的连接 URL 写 `.env.local`,公共后端和地址写目标环境文件;日志展示脱敏值。调用 deploy API 时显式传 `{ environment: 'test', envDir: '/workspace/app' }`,不要用调用者的 `NODE_ENV` 猜测部署目标。
7
+ 环境文件遵循项目的六文件规范:`.env.example` 为带说明/枚举/范围的完整清单,`.env` 为公共基础,`.env.dev/test/prod` 为公共环境覆盖,以上五类提交 Git;`.env.local` 为忽略 Git 的本机私有覆盖。加载顺序为进程 > `.env.local` > 目标环境 > `.env`。真实密码、API Key、含认证信息的连接 URL 写 `.env.local`,公共后端和地址写目标环境文件;CLI 仅报告保存结果,不打印环境变量名或值。调用 deploy API 时显式传 `{ environment: 'test', envDir: '/workspace/app' }`,不要用调用者的 `NODE_ENV` 猜测部署目标。
8
8
 
9
9
  ## 识别与逐项选择
10
10
 
11
+ 数据库连接保存前复用所选后端的公开配置 Schema;MySQL `ssl` 仅 `false` 或严格对象,`{}` 默认验证证书链和 DNS 身份,`ca/cert/key` 为 PEM 内容且 `cert/key` 必须成对;私钥仅进入私有环境。PostgreSQL 保留其布尔值/字符串模式/对象契约。CLI 内联 `@h-ai/reldb/schemas` 与 Zod,无需为配置校验给目标应用额外安装数据库包或驱动;部署本身仍需已安装的 `@h-ai/deploy`,实际运行应用仍需其数据库依赖。
12
+
11
13
  先用 `deploy.scan(appDir, { environment, envDir })` 核对实际依赖、配置与环境值。扫描从目标 workspace 的 `pnpm-lock.yaml` importer 出发,沿 dependencies/optionalDependencies 遍历,排除 devDependencies、peerDependencies 和其他应用未被引用的依赖;workspace link 只沿运行依赖继续。pnpm 12 的工具链 YAML 文档不能混入应用图。lockfile、目标 importer 或依赖节点缺失时先执行 `pnpm install` 修复;依赖图不读取 package.json、pnpm-workspace.yaml 或项目目录布局。配置文件决定已识别模块的后端,保留的旧配置不能自行启用未依赖的模块。
12
14
 
13
15
  `ScanResult.haiModules` 列出所有实际运行依赖中的 Hai 模块;`ScanResult.middlewares` 提供服务、后端、来源、配置路径和变量前缀;连接配置可能含凭据,不能整体记入日志。`DEPLOY_MIDDLEWARE` 是后端和连接字段的公共目录,字段名称、类型与约束须与模块 Schema 同步。
14
16
 
15
- | 来源 | 后端选项 | 默认 Compose 行为 | 云自动开通 |
16
- | --- | --- | --- | --- |
17
- | reldb;IAM/audit;启用 DB 日志的 scheduler | SQLite / MySQL / PostgreSQL | SQLite 数据卷;mysql / postgres 服务 | PostgreSQL → Neon |
18
- | cache;IAM;scheduler 可选分布式锁 | memory / Redis | memory 无服务;redis 服务 | Redis → Upstash |
19
- | storage | local / S3 | 本地数据卷;RustFS(默认)/MinIO + bucket 初始化 | S3 → Cloudflare R2 |
20
- | vecdb | LanceDB / pgvector / Qdrant / Chroma | 本地卷或对应容器 | 自定义已有服务 |
21
- | IAM 可选目录认证 | disabled / LDAP | 禁用无服务;OpenLDAP | 自定义已有目录 |
22
- | Reach Provider 数组 | console / SMTP / HTTP API;阿里云 SMS | console 无服务;SMTP 用 Mailpit 捕获测试邮件;HTTP/SMS 自定义 | SMTP → Resend;SMS → 阿里云凭据接入 |
17
+ | 来源 | 后端选项 | 默认 Compose 行为 | 云自动开通 |
18
+ | ------------------------------------------ | ------------------------------------- | ------------------------------------------------------------- | ----------------------------------- |
19
+ | reldb;IAM/audit;启用 DB 日志的 scheduler | SQLite / MySQL / PostgreSQL | SQLite 数据卷;mysql / postgres 服务 | PostgreSQL → Neon |
20
+ | cache;IAM;scheduler 可选分布式锁 | memory / Redis | memory 无服务;redis 服务 | Redis → Upstash |
21
+ | storage | local / S3 | 本地数据卷;RustFS(默认)/MinIO + bucket 初始化 | S3 → Cloudflare R2 |
22
+ | vecdb | LanceDB / pgvector / Qdrant / Chroma | 本地卷或对应容器 | 自定义已有服务 |
23
+ | IAM 可选目录认证 | disabled / LDAP | 禁用无服务;OpenLDAP | 自定义已有目录 |
24
+ | Reach Provider 数组 | console / SMTP / HTTP API;阿里云 SMS | console 无服务;SMTP 用 Mailpit 捕获测试邮件;HTTP/SMS 自定义 | SMTP → Resend;SMS → 阿里云凭据接入 |
23
25
 
24
- `serv` 声明 AI peer 不代表应用启用了 AI,不应因此安装 AI/ONNX/CUDA 或开通向量服务。LDAP 需要在应用安装 IAM 的可选 peer `ldapts`,并显式设置 `login.ldap=true`;安装 IAM 不代表启用了 LDAP。Reach 保留 Provider 的数组索引及名称,不能把 HTTP 回调当作 SMTP。默认目录没有业务账号;Mailpit 不会对外投递生产邮件。生产应配置企业 LDAP/SMTP,或使用对应云服务。
26
+ `serv` 声明 AI peer 不代表应用启用了 AI,不应因此安装 AI/ONNX/CUDA 或开通向量服务。LDAP 需要在应用安装 IAM 的可选 peer `ldapts`,并显式设置 `login.ldap=true`;安装 IAM 不代表启用了 LDAP。Reach 每个 Provider 保留独立数组索引及名称;通用 Console/HTTP API 按应用模板约定将 `name: sms` 归为短信,其他名称归为邮件服务,SMTP 和 aliyun-sms 按实际 type 分类,不能把 HTTP 回调当作 SMTP。email/sms 的 Console 均只输出日志,不实际投递。默认目录没有业务账号;Mailpit 不会对外投递生产邮件。生产应配置企业 LDAP/SMTP,或使用对应云服务。
25
27
 
26
- 每项都展示当前生效后端和用途,例如 `HAI_DB_TYPE=postgresql` 显示 PostgreSQL,再选择:
28
+ 每项都展示当前生效后端和用途,例如 PostgreSQL,再选择:
27
29
 
28
30
  1. **使用默认配置**:选择后端;Compose 生成匹配容器/卷,云平台调用已支持的 Provisioner,并保存实际连接配置。MySQL、LDAP、向量库等没有云自动开通器时,明确提示改用自定义连接;不能声称已经创建云资源。Resend 需要已验证的发件域名,R2 需要已有 S3 访问密钥,阿里云短信需要签名/模板与额度。
29
- 2. **使用自定义配置**:逐项输入所选后端的连接字段,展示变量名、字段用途与约束,校验必填项、端口、布尔和 URL;凭据用密码输入。保存配置后跳过该服务的开通与 sidecar。
31
+ 2. **使用自定义配置**:逐项输入所选后端的连接字段,展示字段用途与约束,校验必填项、端口、布尔和 URL;凭据用密码输入。保存配置后跳过该服务的开通与 sidecar。
30
32
  3. **保持不变**:仅在目标 `.env.<mode>` 显式声明当前后端及完整连接配置时出现;私有凭据可来自 `.env.local`/进程。选择后不修改该项、不认证其 Provisioner、不创建或移除已有中间件容器,继续下一项。只有 `.env` 或其他环境有配置不满足此条件。
31
33
 
32
34
  逐项配置前提供“环境配置已完备,直接部署”。选择后跳过全部中间件提示、变量改写、云资源与 sidecar 创建,保留已有服务;仍校验已有构建产物并执行部署及健康检查。命令行使用唯一的 `--skip-middleware`,SDK 使用 `skipMiddleware`。不要因为缺少某项中间件的目标文件声明而阻止用户主动跳过。
33
35
 
34
36
  `--yes` 用于已授权的自动化:目标环境已有完整配置时保持不变,否则使用当前后端默认配置;不支持的云后端或缺少必需凭据直接失败。自动化直接部署用 `hai deploy packages/my-app-serv --env prod --yes --skip-middleware`。跳过不作为配置完整性或连接成功的证明。
35
37
 
36
- 部署目标不完整时,先引导选择 SSH/Vercel。SSH 逐项输入主机、端口、用户名、私钥文件路径、known_hosts 路径、远程目录/运行时及容器/主机端口;端口范围 1–65535。密钥路径留空使用 ssh-agent/默认密钥,known_hosts 留空使用 OpenSSH 默认文件。完整目标自动复用;修改 `.env.local` 的 `HAI_DEPLOY_PROVIDER` 可切换连接。首次填写的整个目标对象(包括地址、端口、路径、Token)只保存 `.env.local` 并添加 Git 忽略,不把机器信息写公共环境文件。SDK 使用 `saveDeploymentEnvironment(snapshot, variables, { private: true })` 可显式保存本机参数。禁止读取、复制或打印私钥内容。
38
+ 直接部署的目标不完整时,先引导选择 SSH/Vercel。SSH 逐项输入主机、端口、用户名、私钥文件路径、known_hosts 路径、远程目录/运行时及容器/主机端口;端口范围 1–65535。密钥路径留空使用 ssh-agent/默认密钥,known_hosts 留空使用 OpenSSH 默认文件。完整目标自动复用;修改 `.env.local` 的 `HAI_DEPLOY_PROVIDER` 可切换连接。首次填写的整个目标对象(包括地址、端口、路径、Token)只保存 `.env.local` 并添加 Git 忽略,不把机器信息写公共环境文件。`--bundle-only` 不询问 SSH,连接信息可在后续上传前填写到生成的 `deploy-target.json`。SDK 使用 `saveDeploymentEnvironment(snapshot, variables, { private: true })` 可显式保存本机参数。禁止读取、复制或打印私钥内容。
37
39
 
38
40
  ### Hai 模块与中间件的关系
39
41
 
40
42
  识别依据是运行图中的所有节点,不按应用直接依赖硬编码消费者名单。下面按当前公开依赖和功能说明;新增模块/后端时同步此表、连接目录与真实运行测试。
41
43
 
42
- | 模块 | 中间件来源或外部能力 |
43
- | --- | --- |
44
- | reldb / cache / storage / vecdb | 分别拥有关系库、缓存、对象存储、向量库连接契约 |
45
- | iam / kit | IAM 运行依赖 audit/reldb/cache/crypto;kit 经 IAM 继承。LDAP 按 login.ldap 开关启用,Reach peer 只有另行实际依赖时进入运行图 |
46
- | audit / payment | audit 经 reldb 持久化;payment 经 audit 继承数据库。微信、支付宝、Stripe 是已有商户 API,沿 payment 配置接入,不生成假云服务或容器 |
47
- | scheduler / reach | 两者实际依赖 reldb/cache;Reach 另按启用的 SMTP/HTTP/短信渠道配置 |
48
- | ai | 实际依赖 reldb/vecdb/datapipe。LLM、图像、音频端点及模型来自 AI 配置,不能仅凭包名选择模型镜像或 CUDA;不自动创建付费厂商账号 |
49
- | crypto / serv | crypto 的可选运行依赖按 lockfile 解析,serv 经 crypto 继承;serv 的 IAM/storage/AI peer 不作为启用证据 |
50
- | core / datapipe / api-contract / api-client / ui / capacitor / cli / deploy | 本身没有额外专用中间件;仍沿实际运行依赖继续遍历,例如 api-client/capacitor 的 crypto 分支。UI/设备能力、部署目标/Provisioner 凭据按各自配置处理 |
44
+ | 模块 | 中间件来源或外部能力 |
45
+ | --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
46
+ | reldb / cache / storage / vecdb | 分别拥有关系库、缓存、对象存储、向量库连接契约 |
47
+ | iam / kit | IAM 运行依赖 audit/reldb/cache/crypto;kit 经 IAM 继承。LDAP 按 login.ldap 开关启用,Reach peer 只有另行实际依赖时进入运行图 |
48
+ | audit / payment | audit 经 reldb 持久化;Stripe payment 直接依赖先初始化的共享 reldb 保存渠道映射和幂等结果,审计可选。微信、支付宝、Stripe 是已有商户 API,沿 payment 配置接入,不生成假云服务或容器 |
49
+ | scheduler / reach | 两者实际依赖 reldb/cache;Reach 另按启用的 SMTP/HTTP/短信渠道配置 |
50
+ | ai | 实际依赖 reldb/vecdb/datapipe。LLM、图像、音频端点及模型来自 AI 配置,不能仅凭包名选择模型镜像或 CUDA;不自动创建付费厂商账号 |
51
+ | crypto / serv | crypto 的可选运行依赖按 lockfile 解析,serv 经 crypto 继承;serv 的 IAM/storage/AI peer 不作为启用证据 |
52
+ | core / datapipe / api-contract / api-client / ui / capacitor / cli / deploy | 本身没有额外专用中间件;仍沿实际运行依赖继续遍历,例如 api-client/capacitor 的 crypto 分支。UI/设备能力、部署目标/Provisioner 凭据按各自配置处理 |
51
53
 
52
54
  S3 是协议,不是某个容器产品。默认配置时另选 RustFS/MinIO,保存到 HAI_DEPLOY_CONTAINER_S3ENGINE(rustfs | minio,默认 rustfs),应用始终写 HAI_STORAGE_TYPE=s3 或 HAI_STORAGE。自定义 AWS S3、OSS、R2 等端点不生成 sidecar。镜像使用与 storage 集成测试一致的 RustFS 官方版本和 MinIO 社区版本;存储桶初始化复用应用镜像里的 S3 SDK,无需额外 mc 镜像,必须幂等且不能吞掉失败。
53
55
 
56
+ AI 使用 MySQL 的首次建表需 CREATE 与 INDEX 权限,节点可并发初始化;仅明确重复索引且重查真实结构一致时继续,错误同名索引、权限和其他 DDL 故障必须阻止应用启动。DDL 不回滚,失败后检查初始化结果、逆序清理连接,再修复结构/权限重试,不能删库或吞错视为成功。
57
+
54
58
  ## 连接字段
55
59
 
56
60
  以下是自定义向导的连接字段;完整可用项、枚举与范围仍以项目 `.env.example` 和公开 Schema 为准。变量前缀为 `HAI_DB`、`HAI_CACHE`、`HAI_STORAGE`、`HAI_VECDB`、`HAI_IAM_LDAP`;Reach 使用实际索引 `HAI_REACH_PROVIDERS_<index>`。camelCase 不拆词,例如 `bindPassword` → `BINDPASSWORD`。
57
61
 
58
- | 后端 | 连接字段 |
59
- | --- | --- |
60
- | SQLite | database(文件路径或 `:memory:`) |
61
- | MySQL / PostgreSQL | url(优先)、host、port(1–65535)、database、user、password、ssl |
62
- | Redis | url(优先)、host、port、password、db、tls、keyPrefix、connectTimeout、commandTimeout |
63
- | local / LanceDB | root / path |
64
- | S3 | endpoint、bucket、region、accessKeyId、secretAccessKey、forcePathStyle |
65
- | Qdrant / Chroma | url;Qdrant 可选 apiKey |
66
- | pgvector | url(优先)或 host/port/user/password,以及 database |
67
- | LDAP | url、bindDn、bindPassword、searchBase、searchFilter、usernameAttribute、emailAttribute、displayNameAttribute、useTls、connectTimeout |
68
- | SMTP | host、port、secure、user、pass、from |
69
- | HTTP API | url、method(POST / PUT)、headers(JSON)、timeout |
70
- | 阿里云 SMS | accessKeyId、accessKeySecret、signName、endpoint |
62
+ | 后端 | 连接字段 |
63
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
64
+ | SQLite | database(文件路径或 `:memory:`) |
65
+ | MySQL / PostgreSQL | url(优先)、host、port(1–65535)、database、user、password、ssl |
66
+ | Redis | url(优先)、host、port、password、db、tls、keyPrefix、connectTimeout、commandTimeout |
67
+ | local / LanceDB | root / path |
68
+ | S3 | endpoint、bucket、region、accessKeyId、secretAccessKey、forcePathStyle |
69
+ | Qdrant / Chroma | url;Qdrant 可选 apiKey;Chroma apiKey 用于 Bearer 自托管网关,URL 仅支持 HTTP(S) 根端点,不提供 Cloud 契约 |
70
+ | pgvector | url(优先)或 host/port/user/password,以及 database |
71
+ | LDAP | url、bindDn、bindPassword、searchBase、searchFilter、usernameAttribute、emailAttribute、displayNameAttribute、useTls、connectTimeout |
72
+ | SMTP | host、port、secure、user、pass、from |
73
+ | HTTP API | url、method(POST / PUT)、headers(JSON)、timeout |
74
+ | 阿里云 SMS | accessKeyId、accessKeySecret、signName、endpoint |
71
75
 
72
76
  ## 离线准备与验收
73
77
 
74
78
  构建与部署可分开,也可通过 pnpm run deploy --build 明确串联。构建保持环境无关;部署选择环境并只消费所选镜像。完整包自动包含 Compose 自建中间件镜像,因此导出阶段需要本地 Podman/Docker,缺镜像在打包阶段准备,不能留给隔离服务器下载。
75
79
 
76
- --bundle-only 生成带版本 tar 与 -deploy/ 目录。操作目录含 Windows deploy.ps1、Linux/macOS deploy.sh 和目标配置。隔离服务器搬运并解包后执行 sh apply.sh。每步失败返回非零,结果写 result-<部署ID>.json;只有 HTTP 检查通过才更新服务器 current。
80
+ 部署缓存、带版本 tar 与 -deploy/ 目录默认位于工程根 .hai/deploy/<环境>/;--output 可显式指定其他 tar 路径,相对命令执行目录解析。--bundle-only 只生成交付物,不连接服务器。操作目录含 Windows deploy.ps1、Linux/macOS deploy.sh 和带逐项配置说明的 deploy-target.json;运行上传脚本前先修改并核对该 JSON,两个脚本均读取此文件。本机运行 deploy.sh 需 sh、awk、ssh、scp;sh deploy.sh 主机 [端口] [用户] 可覆盖连接地址,HAI_DEPLOY_IDENTITY_FILE 和 HAI_DEPLOY_KNOWN_HOSTS_FILE 可覆盖两脚本的私钥及指纹文件路径。隔离服务器搬运并解包后执行 sh apply.sh。每步失败返回非零,结果写 result-<部署ID>.json;只有 HTTP 检查通过才更新服务器 current。
77
81
 
78
82
  部分更新通过 --services 指定,使用服务器成功基线保留未选服务和中间件凭据。首次安装不能使用部分包。storage-init 复用应用镜像及 S3 SDK 检查/创建桶,不清空数据;只有涉及存储初始化时执行,并检查退出码。仅更新 storage 仍需含 app 镜像的制品;最近为 web-only 时,用 --artifact-dir 选择原完整制品,或先 pnpm build --target docker --images app。app 镜像仅用于初始化,不会更新 app 服务。失败恢复尝试还原容器和配置,保留数据卷,不回退已写业务数据。
79
83
 
80
- 服务器需要 Linux、Docker/Podman、Compose、sh、tar、sha256sum、awk、curl、持久化磁盘和容器/目录权限。完整交付包含自建服务镜像;自定义外部连接仍需准备服务、账号和网络可达性。云 Provider/Provisioner 需要外网,不能用于完全离线。
84
+ 服务器需要 Linux、Docker/Podman、Compose、sh、tar、sha256sum、awk、curl、util-linux flock、支持 flock 的持久化文件系统和容器/目录权限。部署和恢复使用固定 `.deploy.lock` 内核锁,不可删除/替换或按时间抢锁;活跃子命令继承 FD,全部持有进程退出或重启后自动释放。强杀/断电后的执行状态需核对结果文件、current 和容器,不能将锁释放当作恢复成功。完整交付包含自建服务镜像;自定义外部连接仍需准备服务、账号和网络可达性。云 Provider/Provisioner 需要外网,不能用于完全离线。
85
+
86
+ 生成的 `web.conf` 在 API location 设 `client_max_body_size 2129920`(2 MiB + 32 KiB),用 large_client_header_buffers 4 32k / proxy_buffer_size 32k 容纳 X-Hai-Transport 认证头,并配套 proxy_buffers 8 32k,保证关闭 buffering 时也能通过 Nginx 缓冲区配置校验。请求/响应 proxy buffering 保持关闭;这与 transport 密文预算一致,避免 Nginx 默认上限拦截合法的 1 MiB 明文编码结果。应用仍执行握手 16 KiB、公钥 4 KiB、明文 1 MiB 的有界读取。自有反向代理须允许相同密文预算并传播取消,不替代应用字节限制;大文件上传使用独立路径和明确预算。
81
87
 
82
88
  完全离线构建另需基础镜像、Linux 依赖缓存、pnpm 和匹配驱动/Node ABI/架构/libc 的原生预编译包。构建可用 --sqlite-prebuild-mirror,Podman 代理不可达可用 --no-http-proxy;不要改系统代理或让 deploy 下载编译环境。生产 HTTPS、CORS、证书、DNS 另行准备。实际断网验收登录、刷新、重启和数据持久化。
89
+
90
+ ## 命令输出
91
+
92
+ 内部 `runCommand` / `SshExecutor.exec` 先 UTF-8 解码,再对每条逻辑流跨块精确脱敏;成功 stdout/stderr、错误、onOutput 均为安全结果。`secrets` 不推断全部进程环境,调用方列出秘密及可能输出的编码/转换值;总长不超过 1 Mi UTF-16 单元,脱敏异常失败关闭且不暴露原 cause。`maxCaptureChars` 默认 1048576、最大 16777216,捕获尾部并返回 stdoutTruncated/stderrTruncated,结构化解析先拒绝截断。onOutput 支持 Promise,顺序等待并保持 pipe 背压;异常回调使执行失败。异常终止不释放未完成的秘密尾部。部署进度仅解析每行前 512 字符。超时不能证明远端工作已终止。
93
+
94
+ deadline 包括 helper 启动和输出处理。异常后 `terminationTimeoutMs` 另等待本地终止核验,默认 10000、上限 60000 毫秒。POSIX 用独立继承进程组;Windows 用隐藏且不加载 profile 的 PowerShell/.NET Job,挂起创建 → 分配成功 → 恢复,禁止 breakaway,helper/编译/分配失败拒绝执行。已脱离组的系统 daemon、现有容器和远端服务不在本地确认范围。只有受管理 Job/组无活动成员才是 confirmed;未证明为 unknown,启动前为 not-started。输入、回调和流异常同样经过此路径,已经进入的回调不能撤销,但不再交付新块。
95
+
96
+ 公开 `getDeployCommandState(result.error)` 在失败分支读取原因、本地状态、远端状态、deploymentId 和 resultFile。SSH 中断默认为远端 unknown;docker-ssh 新连接只读解析原子 `result-<ID>.json`,完整/未截断/ID 匹配才为 finished,并返回实际阶段、退出码。finished 指脚本及恢复结束,容器可以继续运行;即使终态为 0,原连接/输出失败仍保留。缺失、旧记录、损坏或重连失败保持 unknown,不自动重试写入。按 ID 核对步骤、current 和服务,再恢复。